prelude-sdk-beta 1398__tar.gz → 1440__tar.gz

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 prelude-sdk-beta might be problematic. Click here for more details.

Files changed (34) hide show
  1. {prelude_sdk_beta-1398 → prelude_sdk_beta-1440}/PKG-INFO +1 -1
  2. {prelude_sdk_beta-1398 → prelude_sdk_beta-1440}/prelude_sdk_beta/controllers/build_controller.py +8 -2
  3. {prelude_sdk_beta-1398 → prelude_sdk_beta-1440}/prelude_sdk_beta/controllers/detect_controller.py +17 -4
  4. {prelude_sdk_beta-1398 → prelude_sdk_beta-1440}/prelude_sdk_beta/controllers/http_controller.py +20 -0
  5. {prelude_sdk_beta-1398 → prelude_sdk_beta-1440}/prelude_sdk_beta/controllers/iam_controller.py +11 -4
  6. {prelude_sdk_beta-1398 → prelude_sdk_beta-1440}/prelude_sdk_beta/controllers/jobs_controller.py +13 -2
  7. {prelude_sdk_beta-1398 → prelude_sdk_beta-1440}/prelude_sdk_beta/controllers/partner_controller.py +1 -13
  8. {prelude_sdk_beta-1398 → prelude_sdk_beta-1440}/prelude_sdk_beta/controllers/scm_controller.py +177 -16
  9. {prelude_sdk_beta-1398 → prelude_sdk_beta-1440}/prelude_sdk_beta/models/account.py +6 -1
  10. {prelude_sdk_beta-1398 → prelude_sdk_beta-1440}/prelude_sdk_beta/models/codes.py +112 -36
  11. {prelude_sdk_beta-1398 → prelude_sdk_beta-1440}/prelude_sdk_beta.egg-info/PKG-INFO +1 -1
  12. {prelude_sdk_beta-1398 → prelude_sdk_beta-1440}/setup.cfg +1 -1
  13. {prelude_sdk_beta-1398 → prelude_sdk_beta-1440}/tests/test_build.py +3 -3
  14. {prelude_sdk_beta-1398 → prelude_sdk_beta-1440}/tests/test_iam.py +1 -1
  15. {prelude_sdk_beta-1398 → prelude_sdk_beta-1440}/tests/test_partner.py +1 -30
  16. {prelude_sdk_beta-1398 → prelude_sdk_beta-1440}/tests/test_scm.py +34 -168
  17. {prelude_sdk_beta-1398 → prelude_sdk_beta-1440}/tests/test_scm_build.py +3 -3
  18. {prelude_sdk_beta-1398 → prelude_sdk_beta-1440}/LICENSE +0 -0
  19. {prelude_sdk_beta-1398 → prelude_sdk_beta-1440}/README.md +0 -0
  20. {prelude_sdk_beta-1398 → prelude_sdk_beta-1440}/prelude_sdk_beta/__init__.py +0 -0
  21. {prelude_sdk_beta-1398 → prelude_sdk_beta-1440}/prelude_sdk_beta/controllers/__init__.py +0 -0
  22. {prelude_sdk_beta-1398 → prelude_sdk_beta-1440}/prelude_sdk_beta/controllers/export_controller.py +0 -0
  23. {prelude_sdk_beta-1398 → prelude_sdk_beta-1440}/prelude_sdk_beta/controllers/generate_controller.py +0 -0
  24. {prelude_sdk_beta-1398 → prelude_sdk_beta-1440}/prelude_sdk_beta/controllers/probe_controller.py +0 -0
  25. {prelude_sdk_beta-1398 → prelude_sdk_beta-1440}/prelude_sdk_beta/models/__init__.py +0 -0
  26. {prelude_sdk_beta-1398 → prelude_sdk_beta-1440}/prelude_sdk_beta.egg-info/SOURCES.txt +0 -0
  27. {prelude_sdk_beta-1398 → prelude_sdk_beta-1440}/prelude_sdk_beta.egg-info/dependency_links.txt +0 -0
  28. {prelude_sdk_beta-1398 → prelude_sdk_beta-1440}/prelude_sdk_beta.egg-info/requires.txt +0 -0
  29. {prelude_sdk_beta-1398 → prelude_sdk_beta-1440}/prelude_sdk_beta.egg-info/top_level.txt +0 -0
  30. {prelude_sdk_beta-1398 → prelude_sdk_beta-1440}/pyproject.toml +0 -0
  31. {prelude_sdk_beta-1398 → prelude_sdk_beta-1440}/tests/test_detect.py +0 -0
  32. {prelude_sdk_beta-1398 → prelude_sdk_beta-1440}/tests/test_generate.py +0 -0
  33. {prelude_sdk_beta-1398 → prelude_sdk_beta-1440}/tests/test_probe.py +0 -0
  34. {prelude_sdk_beta-1398 → prelude_sdk_beta-1440}/tests/testutils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: prelude-sdk-beta
3
- Version: 1398
3
+ Version: 1440
4
4
  Summary: For interacting with the Prelude API
5
5
  Home-page: https://github.com/preludeorg
6
6
  Author: Prelude Research
@@ -271,7 +271,10 @@ class BuildController(HttpController):
271
271
  headers=self.account.headers,
272
272
  timeout=10,
273
273
  )
274
- return res.json()
274
+ threat_hunt = res.json()
275
+ if self.account.resolve_enums:
276
+ self.resolve_enums(threat_hunt, [(Control, "control")])
277
+ return threat_hunt
275
278
 
276
279
  @verify_credentials
277
280
  def update_threat_hunt(
@@ -296,7 +299,10 @@ class BuildController(HttpController):
296
299
  headers=self.account.headers,
297
300
  timeout=10,
298
301
  )
299
- return res.json()
302
+ threat_hunt = res.json()
303
+ if self.account.resolve_enums:
304
+ self.resolve_enums(threat_hunt, [(Control, "control")])
305
+ return threat_hunt
300
306
 
301
307
  @verify_credentials
302
308
  def delete_threat_hunt(self, threat_hunt_id: str):
@@ -1,5 +1,6 @@
1
1
  from prelude_sdk_beta.controllers.http_controller import HttpController
2
2
  from prelude_sdk_beta.models.account import verify_credentials
3
+ from prelude_sdk_beta.models.codes import Control, RunCode
3
4
 
4
5
 
5
6
  class DetectController(HttpController):
@@ -59,7 +60,10 @@ class DetectController(HttpController):
59
60
  params=params,
60
61
  timeout=10,
61
62
  )
62
- return res.json()
63
+ endpoints = res.json()
64
+ if self.account.resolve_enums:
65
+ self.resolve_enums(endpoints, [(Control, "control")])
66
+ return endpoints
63
67
 
64
68
  @verify_credentials
65
69
  def describe_activity(self, filters: dict, view: str = "protected"):
@@ -169,7 +173,10 @@ class DetectController(HttpController):
169
173
  params=filters if filters else {},
170
174
  timeout=10,
171
175
  )
172
- return res.json()
176
+ threat_hunts = res.json()
177
+ if self.account.resolve_enums:
178
+ self.resolve_enums(threat_hunts, [(Control, "control")])
179
+ return threat_hunts
173
180
 
174
181
  @verify_credentials
175
182
  def get_threat_hunt(self, threat_hunt_id):
@@ -179,7 +186,10 @@ class DetectController(HttpController):
179
186
  headers=self.account.headers,
180
187
  timeout=10,
181
188
  )
182
- return res.json()
189
+ threat_hunt = res.json()
190
+ if self.account.resolve_enums:
191
+ self.resolve_enums(threat_hunt, [(Control, "control")])
192
+ return threat_hunt
183
193
 
184
194
  @verify_credentials
185
195
  def do_threat_hunt(self, threat_hunt_id):
@@ -214,7 +224,10 @@ class DetectController(HttpController):
214
224
  json=dict(items=items),
215
225
  timeout=10,
216
226
  )
217
- return res.json()
227
+ schedule = res.json()
228
+ if self.account.resolve_enums:
229
+ self.resolve_enums(schedule, [(RunCode, "run_code")])
230
+ return schedule
218
231
 
219
232
  @verify_credentials
220
233
  def unschedule(self, items: list):
@@ -22,6 +22,26 @@ class HttpController(object):
22
22
  self._session.mount("http://", HTTPAdapter(max_retries=retry))
23
23
  self._session.mount("https://", HTTPAdapter(max_retries=retry))
24
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
+
25
45
  def get(self, url, retry=True, **kwargs):
26
46
  res = self._session.get(url, **kwargs)
27
47
  if res.status_code == 200:
@@ -1,6 +1,6 @@
1
1
  from prelude_sdk_beta.controllers.http_controller import HttpController
2
2
  from prelude_sdk_beta.models.account import verify_credentials
3
- from prelude_sdk_beta.models.codes import Mode, Permission
3
+ from prelude_sdk_beta.models.codes import Control, Mode, Permission
4
4
 
5
5
 
6
6
  class IAMAccountController(HttpController):
@@ -14,7 +14,12 @@ class IAMAccountController(HttpController):
14
14
  res = self.get(
15
15
  f"{self.account.hq}/iam/account", headers=self.account.headers, timeout=10
16
16
  )
17
- return res.json()
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
18
23
 
19
24
  @verify_credentials
20
25
  def purge_account(self):
@@ -100,7 +105,10 @@ class IAMAccountController(HttpController):
100
105
  headers=self.account.headers,
101
106
  timeout=10,
102
107
  )
103
- return res.json()
108
+ user = res.json()
109
+ if self.account.resolve_enums:
110
+ self.resolve_enums(user, [(Permission, "permission")])
111
+ return user
104
112
 
105
113
  @verify_credentials
106
114
  def create_service_user(self, name: str):
@@ -173,7 +181,6 @@ class IAMAccountController(HttpController):
173
181
  )
174
182
  return res.json()
175
183
 
176
-
177
184
  def sign_up(self, company, email, name):
178
185
  """(NOT AVAIABLE IN PRODUCTION) Create a new user and account"""
179
186
  body = dict(company=company, email=email, name=name)
@@ -1,5 +1,8 @@
1
+ from itertools import chain
2
+
1
3
  from prelude_sdk_beta.controllers.http_controller import HttpController
2
4
  from prelude_sdk_beta.models.account import verify_credentials
5
+ from prelude_sdk_beta.models.codes import BackgroundJobTypes, Control
3
6
 
4
7
 
5
8
  class JobsController(HttpController):
@@ -13,7 +16,10 @@ class JobsController(HttpController):
13
16
  res = self.get(
14
17
  f"{self.account.hq}/jobs/statuses", headers=self.account.headers, timeout=30
15
18
  )
16
- return res.json()
19
+ jobs = res.json()
20
+ if self.account.resolve_enums:
21
+ self.resolve_enums(jobs, [(Control, "control")])
22
+ return jobs
17
23
 
18
24
  @verify_credentials
19
25
  def job_status(self, job_id: str):
@@ -23,4 +29,9 @@ class JobsController(HttpController):
23
29
  headers=self.account.headers,
24
30
  timeout=30,
25
31
  )
26
- return res.json()
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
@@ -45,7 +45,7 @@ class PartnerController(HttpController):
45
45
  res = self.delete(
46
46
  f"{self.account.hq}/partner/{partner.name}/{instance_id}",
47
47
  headers=self.account.headers,
48
- timeout=10,
48
+ timeout=30,
49
49
  )
50
50
  return res.json()
51
51
 
@@ -104,18 +104,6 @@ class PartnerController(HttpController):
104
104
  )
105
105
  return res.json()
106
106
 
107
- @verify_credentials
108
- def ioa_stats(self, test_id: str | None = None):
109
- """Get IOA stats"""
110
- params = dict(test_id=test_id) if test_id else dict()
111
- res = self.get(
112
- f"{self.account.hq}/partner/ioa_stats",
113
- headers=self.account.headers,
114
- json=params,
115
- timeout=30,
116
- )
117
- return res.json()
118
-
119
107
  @verify_credentials
120
108
  def observed_detected(self, test_id: str | None = None, hours: int | None = None):
121
109
  """Get observed_detected stats"""
@@ -1,10 +1,18 @@
1
1
  from prelude_sdk_beta.controllers.http_controller import HttpController
2
2
  from prelude_sdk_beta.models.account import verify_credentials
3
- from prelude_sdk_beta.models.codes import Control, ControlCategory, PartnerEvents, RunCode
3
+ from prelude_sdk_beta.models.codes import (
4
+ Control,
5
+ ControlCategory,
6
+ PartnerEvents,
7
+ PolicyType,
8
+ NotationType,
9
+ RunCode,
10
+ SCMCategory,
11
+ )
4
12
 
5
13
 
6
14
  class ScmController(HttpController):
7
- default = -1
15
+ default = "-1"
8
16
 
9
17
  def __init__(self, account):
10
18
  super().__init__(account)
@@ -19,7 +27,18 @@ class ScmController(HttpController):
19
27
  params=params,
20
28
  timeout=30,
21
29
  )
22
- return res.json()
30
+ data = res.json()
31
+ if self.account.resolve_enums:
32
+ self.resolve_enums(
33
+ data,
34
+ [
35
+ (Control, "controls"),
36
+ (Control, "control"),
37
+ (ControlCategory, "category"),
38
+ (PartnerEvents, "event"),
39
+ ],
40
+ )
41
+ return data
23
42
 
24
43
  @verify_credentials
25
44
  def inboxes(self, filter: str = None, orderby: str = None, top: int = None):
@@ -31,7 +50,41 @@ class ScmController(HttpController):
31
50
  params=params,
32
51
  timeout=30,
33
52
  )
34
- return res.json()
53
+ data = res.json()
54
+ if self.account.resolve_enums:
55
+ self.resolve_enums(
56
+ data,
57
+ [
58
+ (Control, "controls"),
59
+ (Control, "control"),
60
+ (ControlCategory, "category"),
61
+ (PartnerEvents, "event"),
62
+ ],
63
+ )
64
+ return data
65
+
66
+ @verify_credentials
67
+ def network_devices(self, filter: str = None, orderby: str = None, top: int = None):
68
+ """List network_devices with SCM analysis"""
69
+ params = {"$filter": filter, "$orderby": orderby, "$top": top}
70
+ res = self.get(
71
+ f"{self.account.hq}/scm/network_devices",
72
+ headers=self.account.headers,
73
+ params=params,
74
+ timeout=30,
75
+ )
76
+ data = res.json()
77
+ if self.account.resolve_enums:
78
+ self.resolve_enums(
79
+ data,
80
+ [
81
+ (Control, "controls"),
82
+ (Control, "control"),
83
+ (ControlCategory, "category"),
84
+ (PartnerEvents, "event"),
85
+ ],
86
+ )
87
+ return data
35
88
 
36
89
  @verify_credentials
37
90
  def users(self, filter: str = None, orderby: str = None, top: int = None):
@@ -43,7 +96,18 @@ class ScmController(HttpController):
43
96
  params=params,
44
97
  timeout=30,
45
98
  )
46
- return res.json()
99
+ data = res.json()
100
+ if self.account.resolve_enums:
101
+ self.resolve_enums(
102
+ data,
103
+ [
104
+ (Control, "controls"),
105
+ (Control, "control"),
106
+ (ControlCategory, "category"),
107
+ (PartnerEvents, "event"),
108
+ ],
109
+ )
110
+ return data
47
111
 
48
112
  @verify_credentials
49
113
  def technique_summary(self, techniques: str):
@@ -78,7 +142,12 @@ class ScmController(HttpController):
78
142
  headers=self.account.headers,
79
143
  timeout=30,
80
144
  )
81
- return res.json()
145
+ data = res.json()
146
+ if self.account.resolve_enums:
147
+ self.resolve_enums(
148
+ data, [(Control, "control"), (ControlCategory, "category")]
149
+ )
150
+ return data
82
151
 
83
152
  @verify_credentials
84
153
  def evaluation(
@@ -86,10 +155,13 @@ class ScmController(HttpController):
86
155
  partner: Control,
87
156
  instance_id: str,
88
157
  filter: str = None,
158
+ policy_types: str = None,
89
159
  techniques: str = None,
90
160
  ):
91
161
  """Get policy evaluations for given partner"""
92
162
  params = {"$filter": filter}
163
+ if policy_types:
164
+ params["policy_types"] = policy_types
93
165
  if techniques:
94
166
  params["techniques"] = techniques
95
167
  res = self.get(
@@ -98,7 +170,10 @@ class ScmController(HttpController):
98
170
  headers=self.account.headers,
99
171
  timeout=30,
100
172
  )
101
- return res.json()
173
+ data = res.json()
174
+ if self.account.resolve_enums:
175
+ self.resolve_enums(data, [(PolicyType, "policy_type")])
176
+ return data
102
177
 
103
178
  @verify_credentials
104
179
  def update_evaluation(self, partner: Control, instance_id: str):
@@ -120,7 +195,10 @@ class ScmController(HttpController):
120
195
  params=params,
121
196
  timeout=10,
122
197
  )
123
- return res.json()
198
+ groups = res.json()
199
+ if self.account.resolve_enums:
200
+ self.resolve_enums(groups, [(Control, "control")])
201
+ return groups
124
202
 
125
203
  @verify_credentials
126
204
  def update_partner_groups(
@@ -144,7 +222,10 @@ class ScmController(HttpController):
144
222
  headers=self.account.headers,
145
223
  timeout=10,
146
224
  )
147
- return res.json()
225
+ exceptions = res.json()
226
+ if self.account.resolve_enums:
227
+ self.resolve_enums(exceptions, [(ControlCategory, "category")])
228
+ return exceptions
148
229
 
149
230
  @verify_credentials
150
231
  def create_object_exception(
@@ -202,13 +283,18 @@ class ScmController(HttpController):
202
283
  headers=self.account.headers,
203
284
  timeout=10,
204
285
  )
205
- return res.json()
286
+ exceptions = res.json()
287
+ if self.account.resolve_enums:
288
+ self.resolve_enums(
289
+ exceptions, [(Control, "control"), (ControlCategory, "category")]
290
+ )
291
+ return exceptions
206
292
 
207
293
  @verify_credentials
208
- def put_policy_exceptions(
209
- self, partner: Control, expires, instance_id: str, policy_id, setting_names
294
+ def create_policy_exception(
295
+ self, partner: Control, instance_id: str, policy_id, setting_names, expires=None
210
296
  ):
211
- """Put policy exceptions"""
297
+ """Create policy exceptions"""
212
298
  body = dict(
213
299
  control=partner.name,
214
300
  expires=expires,
@@ -216,6 +302,29 @@ class ScmController(HttpController):
216
302
  policy_id=policy_id,
217
303
  setting_names=setting_names,
218
304
  )
305
+ res = self.post(
306
+ f"{self.account.hq}/scm/exceptions/policies",
307
+ json=body,
308
+ headers=self.account.headers,
309
+ timeout=10,
310
+ )
311
+ return res.json()
312
+
313
+ @verify_credentials
314
+ def update_policy_exception(
315
+ self,
316
+ partner: Control,
317
+ instance_id: str,
318
+ policy_id,
319
+ expires=default,
320
+ setting_names=None,
321
+ ):
322
+ """Update policy exceptions"""
323
+ body = dict(control=partner.name, instance_id=instance_id, policy_id=policy_id)
324
+ if expires != self.default:
325
+ body["expires"] = expires
326
+ if setting_names:
327
+ body["setting_names"] = setting_names
219
328
  res = self.put(
220
329
  f"{self.account.hq}/scm/exceptions/policies",
221
330
  json=body,
@@ -224,6 +333,18 @@ class ScmController(HttpController):
224
333
  )
225
334
  return res.json()
226
335
 
336
+ @verify_credentials
337
+ def delete_policy_exception(self, instance_id: str, policy_id: str):
338
+ """Delete policy exceptions"""
339
+ body = dict(instance_id=instance_id, policy_id=policy_id)
340
+ res = self.delete(
341
+ f"{self.account.hq}/scm/exceptions/policies",
342
+ json=body,
343
+ headers=self.account.headers,
344
+ timeout=10,
345
+ )
346
+ return res.json()
347
+
227
348
  @verify_credentials
228
349
  def list_views(self):
229
350
  """List views"""
@@ -232,7 +353,10 @@ class ScmController(HttpController):
232
353
  headers=self.account.headers,
233
354
  timeout=10,
234
355
  )
235
- return res.json()
356
+ views = res.json()
357
+ if self.account.resolve_enums:
358
+ self.resolve_enums(views, [(ControlCategory, "category")])
359
+ return views
236
360
 
237
361
  @verify_credentials
238
362
  def create_view(self, category: ControlCategory, filter: str, name: str):
@@ -371,7 +495,17 @@ class ScmController(HttpController):
371
495
  headers=self.account.headers,
372
496
  timeout=10,
373
497
  )
374
- return res.json()
498
+ notifications = res.json()
499
+ if self.account.resolve_enums:
500
+ self.resolve_enums(
501
+ notifications,
502
+ [
503
+ (ControlCategory, "control_category"),
504
+ (PartnerEvents, "event"),
505
+ (RunCode, "run_code"),
506
+ ],
507
+ )
508
+ return notifications
375
509
 
376
510
  @verify_credentials
377
511
  def delete_notification(self, notification_id: str):
@@ -431,4 +565,31 @@ class ScmController(HttpController):
431
565
  headers=self.account.headers,
432
566
  timeout=10,
433
567
  )
434
- return res.json()
568
+ notations = res.json()
569
+ if self.account.resolve_enums:
570
+ self.resolve_enums(notations, [(NotationType, "event")])
571
+ return notations
572
+
573
+ @verify_credentials
574
+ def list_history(
575
+ self, start_date: str = None, end_date: str = None, filter: str = None
576
+ ):
577
+ """List history"""
578
+ params = {"start_date": start_date, "end_date": end_date, "$filter": filter}
579
+ res = self.get(
580
+ f"{self.account.hq}/scm/history",
581
+ headers=self.account.headers,
582
+ params=params,
583
+ timeout=10,
584
+ )
585
+ history = res.json()
586
+ if self.account.resolve_enums:
587
+ self.resolve_enums(
588
+ history,
589
+ [
590
+ (Control, "control"),
591
+ (PartnerEvents, "event"),
592
+ (SCMCategory, "category"),
593
+ ],
594
+ )
595
+ return history
@@ -83,7 +83,7 @@ def exchange_token(
83
83
  class Account:
84
84
 
85
85
  @staticmethod
86
- def from_keychain(profile: str = "default"):
86
+ def from_keychain(profile: str = "default", resolve_enums: bool = False):
87
87
  """
88
88
  Create an account object from a pre-configured profile in your keychain file
89
89
  """
@@ -100,6 +100,7 @@ class Account:
100
100
  oidc=profile_items.get("oidc"),
101
101
  profile=profile,
102
102
  slug=profile_items.get("slug"),
103
+ resolve_enums=resolve_enums,
103
104
  )
104
105
 
105
106
  @staticmethod
@@ -111,6 +112,7 @@ class Account:
111
112
  hq: str = "https://api.us1.preludesecurity.com",
112
113
  oidc: str | None = None,
113
114
  slug: str | None = None,
115
+ resolve_enums: bool = False,
114
116
  ):
115
117
  """
116
118
  Create an account object from an access token or a refresh token
@@ -131,6 +133,7 @@ class Account:
131
133
  slug=slug,
132
134
  token=token,
133
135
  token_location=None,
136
+ resolve_enums=resolve_enums,
134
137
  )
135
138
 
136
139
 
@@ -151,6 +154,7 @@ class _Account:
151
154
  token_location: str | None = os.path.join(
152
155
  Path.home(), ".prelude", "tokens.json"
153
156
  ),
157
+ resolve_enums: bool = False,
154
158
  ):
155
159
  if token is None and token_location is None:
156
160
  raise ValueError(
@@ -168,6 +172,7 @@ class _Account:
168
172
  self.slug = slug
169
173
  self.token = token
170
174
  self.token_location = token_location
175
+ self.resolve_enums = resolve_enums
171
176
  if self.token_location and not os.path.exists(self.token_location):
172
177
  head, _ = os.path.split(Path(self.token_location))
173
178
  Path(head).mkdir(parents=True, exist_ok=True)
@@ -184,6 +184,9 @@ class Control(Enum, metaclass=MissingItem):
184
184
  QUALYS_DISCOVERY = 24
185
185
  RAPID7 = 25
186
186
  RAPID7_DISCOVERY = 26
187
+ INTEL_INTUNE = 28
188
+ CISCO_MERAKI = 29
189
+ CISCO_MERAKI_IDENTITY = 30
187
190
 
188
191
  @classmethod
189
192
  def _missing_(cls, value):
@@ -203,6 +206,36 @@ class Control(Enum, metaclass=MissingItem):
203
206
  return k
204
207
  return SCMCategory.NONE
205
208
 
209
+ @property
210
+ def parent(self):
211
+ match self:
212
+ case Control.CISCO_MERAKI_IDENTITY:
213
+ return Control.CISCO_MERAKI
214
+ case Control.DEFENDER_DISCOVERY:
215
+ return Control.DEFENDER
216
+ case Control.QUALYS_DISCOVERY:
217
+ return Control.QUALYS
218
+ case Control.RAPID7_DISCOVERY:
219
+ return Control.RAPID7
220
+ case Control.TENABLE_DISCOVERY:
221
+ return Control.TENABLE
222
+
223
+ @property
224
+ def children(self):
225
+ match self:
226
+ case Control.CISCO_MERAKI:
227
+ return [Control.CISCO_MERAKI_IDENTITY]
228
+ case Control.DEFENDER:
229
+ return [Control.DEFENDER_DISCOVERY]
230
+ case Control.QUALYS:
231
+ return [Control.QUALYS_DISCOVERY]
232
+ case Control.RAPID7:
233
+ return [Control.RAPID7_DISCOVERY]
234
+ case Control.TENABLE:
235
+ return [Control.TENABLE_DISCOVERY]
236
+ case _:
237
+ return []
238
+
206
239
 
207
240
  class ControlCategory(Enum, metaclass=MissingItem):
208
241
  INVALID = -1
@@ -217,6 +250,7 @@ class ControlCategory(Enum, metaclass=MissingItem):
217
250
  VULN_MANAGER = 8
218
251
  SIEM = 9
219
252
  PRIVATE_REPO = 10
253
+ HARDWARE = 11
220
254
 
221
255
  @classmethod
222
256
  def _missing_(cls, value):
@@ -244,12 +278,16 @@ class ControlCategory(Enum, metaclass=MissingItem):
244
278
  Control.GMAIL,
245
279
  Control.M365,
246
280
  ],
281
+ ControlCategory.HARDWARE: [
282
+ Control.INTEL_INTUNE,
283
+ ],
247
284
  ControlCategory.IDENTITY: [
285
+ Control.CISCO_MERAKI_IDENTITY,
248
286
  Control.ENTRA,
249
287
  Control.GOOGLE_IDENTITY,
250
288
  Control.OKTA,
251
289
  ],
252
- ControlCategory.NETWORK: [],
290
+ ControlCategory.NETWORK: [Control.CISCO_MERAKI],
253
291
  ControlCategory.PRIVATE_REPO: [
254
292
  Control.GITHUB,
255
293
  ],
@@ -277,6 +315,7 @@ class SCMCategory(Enum, metaclass=MissingItem):
277
315
  ENDPOINT = 1
278
316
  INBOX = 2
279
317
  USER = 3
318
+ NETWORK_DEVICE = 4
280
319
 
281
320
  @classmethod
282
321
  def _missing_(cls, value):
@@ -292,6 +331,7 @@ class SCMCategory(Enum, metaclass=MissingItem):
292
331
  Control.DEFENDER,
293
332
  Control.DEFENDER_DISCOVERY,
294
333
  Control.EC2,
334
+ Control.INTEL_INTUNE,
295
335
  Control.INTUNE,
296
336
  Control.JAMF,
297
337
  Control.QUALYS,
@@ -303,15 +343,19 @@ class SCMCategory(Enum, metaclass=MissingItem):
303
343
  Control.TENABLE,
304
344
  Control.TENABLE_DISCOVERY,
305
345
  ],
346
+ SCMCategory.INBOX: [
347
+ Control.GMAIL,
348
+ Control.M365,
349
+ ],
350
+ SCMCategory.NETWORK_DEVICE: [
351
+ Control.CISCO_MERAKI,
352
+ ],
306
353
  SCMCategory.USER: [
354
+ Control.CISCO_MERAKI_IDENTITY,
307
355
  Control.ENTRA,
308
356
  Control.GOOGLE_IDENTITY,
309
357
  Control.OKTA,
310
358
  ],
311
- SCMCategory.INBOX: [
312
- Control.GMAIL,
313
- Control.M365,
314
- ],
315
359
  }
316
360
 
317
361
  @classmethod
@@ -320,11 +364,13 @@ class SCMCategory(Enum, metaclass=MissingItem):
320
364
  SCMCategory.ENDPOINT: [
321
365
  ControlCategory.ASSET_MANAGER,
322
366
  ControlCategory.DISCOVERED_DEVICES,
367
+ ControlCategory.HARDWARE,
323
368
  ControlCategory.VULN_MANAGER,
324
369
  ControlCategory.XDR,
325
370
  ],
326
- SCMCategory.USER: [ControlCategory.IDENTITY],
327
371
  SCMCategory.INBOX: [ControlCategory.EMAIL],
372
+ SCMCategory.NETWORK_DEVICE: [ControlCategory.NETWORK],
373
+ SCMCategory.USER: [ControlCategory.IDENTITY],
328
374
  }
329
375
 
330
376
 
@@ -356,19 +402,27 @@ class EDRResponse(Enum, metaclass=MissingItem):
356
402
  class PartnerEvents(Enum, metaclass=MissingItem):
357
403
  INVALID = -1
358
404
  REDUCED_FUNCTIONALITY_MODE = 1
359
- NO_EDR = 2
360
- MISSING_EDR_POLICY = 3
361
- MISSING_AV_POLICY = 4
405
+ MISSING_EDR = 2
406
+ NO_EDR_POLICY = 3
407
+ NO_AV_POLICY = 4
362
408
  MISSING_MFA = 5
363
- NO_ASSET_MANAGER = 6
409
+ MISSING_ASSET_MANAGER = 6
364
410
  MISCONFIGURED_POLICY_SETTING = 7
365
- MISSING_SCAN = 8
411
+ MISSING_VULN_SCAN = 8
366
412
  OUT_OF_DATE_SCAN = 9
367
- NO_VULN_MANAGER = 10
413
+ MISSING_VULN_MANAGER = 10
368
414
  USER_MISSING_ASSET_MANAGER = 11
369
415
  USER_MISSING_EDR = 12
370
416
  USER_MISSING_VULN_MANAGER = 13
371
- NO_SERVER_MANAGER = 14
417
+ MISSING_SERVER_MANAGER = 14
418
+ NO_HOST_FIREWALL_POLICY = 16
419
+ OUT_OF_DATE_FIRMWARE = 18
420
+ NO_DISK_ENCRYPTION_POLICY = 19
421
+ NO_DISK_ENCRYPTION = 20
422
+ NO_REGISTERED_DEVICES = 21
423
+ NO_DEVICE_COMPLIANCE_POLICY = 22
424
+ NONCOMPLIANT = 23
425
+ NO_ASR_POLICY = 24
372
426
 
373
427
  @classmethod
374
428
  def _missing_(cls, value):
@@ -377,46 +431,61 @@ class PartnerEvents(Enum, metaclass=MissingItem):
377
431
  @classmethod
378
432
  def control_category_mapping(cls):
379
433
  return {
380
- PartnerEvents.REDUCED_FUNCTIONALITY_MODE: [ControlCategory.XDR],
381
- PartnerEvents.NO_EDR: [
382
- ControlCategory.XDR,
383
- ],
384
- PartnerEvents.MISSING_EDR_POLICY: [ControlCategory.XDR],
385
- PartnerEvents.MISSING_AV_POLICY: [ControlCategory.XDR],
386
- PartnerEvents.MISSING_MFA: [ControlCategory.IDENTITY],
387
- PartnerEvents.NO_ASSET_MANAGER: [ControlCategory.ASSET_MANAGER],
388
434
  PartnerEvents.MISCONFIGURED_POLICY_SETTING: [
389
- ControlCategory.XDR,
435
+ ControlCategory.ASSET_MANAGER,
390
436
  ControlCategory.EMAIL,
391
437
  ControlCategory.IDENTITY,
438
+ ControlCategory.XDR,
392
439
  ],
393
- PartnerEvents.MISSING_SCAN: [ControlCategory.VULN_MANAGER],
440
+ PartnerEvents.MISSING_ASSET_MANAGER: [ControlCategory.ASSET_MANAGER],
441
+ PartnerEvents.MISSING_EDR: [ControlCategory.XDR],
442
+ PartnerEvents.MISSING_MFA: [ControlCategory.IDENTITY],
443
+ PartnerEvents.MISSING_SERVER_MANAGER: [ControlCategory.ASSET_MANAGER],
444
+ PartnerEvents.MISSING_VULN_MANAGER: [ControlCategory.VULN_MANAGER],
445
+ PartnerEvents.MISSING_VULN_SCAN: [ControlCategory.VULN_MANAGER],
446
+ PartnerEvents.NO_ASR_POLICY: [ControlCategory.ASSET_MANAGER],
447
+ PartnerEvents.NO_AV_POLICY: [ControlCategory.XDR],
448
+ PartnerEvents.NO_DEVICE_COMPLIANCE_POLICY: [ControlCategory.ASSET_MANAGER],
449
+ PartnerEvents.NO_DISK_ENCRYPTION: [ControlCategory.ASSET_MANAGER],
450
+ PartnerEvents.NO_DISK_ENCRYPTION_POLICY: [ControlCategory.ASSET_MANAGER],
451
+ PartnerEvents.NO_EDR_POLICY: [ControlCategory.XDR],
452
+ PartnerEvents.NO_HOST_FIREWALL_POLICY: [ControlCategory.ASSET_MANAGER],
453
+ PartnerEvents.NO_REGISTERED_DEVICES: [ControlCategory.IDENTITY],
454
+ PartnerEvents.NONCOMPLIANT: [ControlCategory.ASSET_MANAGER],
455
+ PartnerEvents.OUT_OF_DATE_FIRMWARE: [ControlCategory.NETWORK],
394
456
  PartnerEvents.OUT_OF_DATE_SCAN: [ControlCategory.VULN_MANAGER],
395
- PartnerEvents.NO_VULN_MANAGER: [ControlCategory.VULN_MANAGER],
457
+ PartnerEvents.REDUCED_FUNCTIONALITY_MODE: [ControlCategory.XDR],
396
458
  PartnerEvents.USER_MISSING_ASSET_MANAGER: [ControlCategory.IDENTITY],
397
459
  PartnerEvents.USER_MISSING_EDR: [ControlCategory.IDENTITY],
398
460
  PartnerEvents.USER_MISSING_VULN_MANAGER: [ControlCategory.IDENTITY],
399
- PartnerEvents.NO_SERVER_MANAGER: [ControlCategory.ASSET_MANAGER],
400
461
  }
401
462
 
402
463
 
403
464
  class AlertTypes(Enum, metaclass=MissingItem):
404
465
  INVALID = -1
405
466
  NEW_REDUCED_FUNCTIONALITY_MODE_ENDPOINTS = 1
406
- NEW_NO_EDR_ENDPOINTS = 2
407
- NEW_MISSING_EDR_POLICY_ENDPOINTS = 3
408
- NEW_MISSING_AV_POLICY_ENDPOINTS = 4
467
+ NEW_MISSING_EDR_ENDPOINTS = 2
468
+ NEW_NO_EDR_POLICY_ENDPOINTS = 3
469
+ NEW_NO_AV_POLICY_ENDPOINTS = 4
409
470
  NEW_MISSING_MFA_USERS = 5
410
- NEW_NO_ASSET_MANAGER_ENDPOINTS = 6
471
+ NEW_MISSING_ASSET_MANAGER_ENDPOINTS = 6
411
472
  NEW_POLICY_SETTING_FAILURE = 7
412
473
  NEW_POLICY_SETTING_PASS = 8
413
- NEW_MISSING_SCAN_ENDPOINTS = 9
414
- NEW_NO_VULN_MANAGER_ENDPOINTS = 10
474
+ NEW_MISSING_VULN_SCAN_ENDPOINTS = 9
475
+ NEW_MISSING_VULN_MANAGER_ENDPOINTS = 10
415
476
  NEW_OUT_OF_DATE_SCAN_ENDPOINTS = 11
416
- NEW_USER_MISSING_ASSET_MANAGER = 12
417
- NEW_USER_MISSING_EDR = 13
418
- NEW_USER_MISSING_VULN_MANAGER = 14
419
- NEW_NO_SERVER_MANAGER_ENDPOINTS = 15
477
+ NEW_MISSING_ASSET_MANAGER_USERS = 12
478
+ NEW_MISSING_EDR_USERS = 13
479
+ NEW_MISSING_VULN_MANAGER_USERS = 14
480
+ NEW_MISSING_SERVER_MANAGER_ENDPOINTS = 15
481
+ NEW_NO_HOST_FIREWALL_POLICY_ENDPOINTS = 17
482
+ NEW_OUT_OF_DATE_FIRMWARE_NETWORK_DEVICES = 19
483
+ NEW_NO_DISK_ENCRYPTION_POLICY_ENDPOINTS = 20
484
+ NEW_NO_DISK_ENCRYPTION_ENDPOINTS = 21
485
+ NEW_NO_REGISTERED_DEVICES_USERS = 22
486
+ NEW_NO_DEVICE_COMPLIANCE_POLICY_ENDPOINTS = 23
487
+ NEW_NONCOMPLIANT_ENDPOINTS = 24
488
+ NEW_NO_ASR_POLICY_ENDPOINTS = 25
420
489
 
421
490
  @classmethod
422
491
  def _missing_(cls, value):
@@ -437,12 +506,19 @@ class PolicyType(Enum, metaclass=MissingItem):
437
506
  EMAIL_DKIM = 10
438
507
  DEVICE_COMPLIANCE = 11
439
508
  IDENTITY_MFA = 12
509
+ HOST_FIREWALL = 13
510
+ NETWORK_FIREWALL = 15
511
+ INTEL_BELOW_OS = 16
512
+ INTEL_OS = 17
513
+ INTEL_TDT = 18
514
+ INTEL_CHIP = 19
515
+ DISK_ENCRYPTION = 20
516
+ ASR = 21
440
517
 
441
518
  @classmethod
442
519
  def _missing_(cls, value):
443
520
  return PolicyType.INVALID
444
521
 
445
-
446
522
  class Platform(Enum, metaclass=MissingItem):
447
523
  INVALID = 0
448
524
  WINDOWS = 1
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: prelude-sdk-beta
3
- Version: 1398
3
+ Version: 1440
4
4
  Summary: For interacting with the Prelude API
5
5
  Home-page: https://github.com/preludeorg
6
6
  Author: Prelude Research
@@ -1,6 +1,6 @@
1
1
  [metadata]
2
2
  name = prelude-sdk-beta
3
- version = 1398
3
+ version = 1440
4
4
  author = Prelude Research
5
5
  author_email = support@preludesecurity.com
6
6
  description = For interacting with the Prelude API
@@ -365,7 +365,7 @@ class TestThreatHunt:
365
365
  control=Control.CROWDSTRIKE.value,
366
366
  id=pytest.crwd_threat_hunt_id,
367
367
  name="test CRWD threat hunt",
368
- query="""#repo=base_sensor | FilePath = "\\Device\\HarddiskVolume3\\Program Files\\Prelude Security\\Prelude Probe\\.vst\\" | ContextImageFileName = /prelude_dropper.exe/""",
368
+ query="#repo=base_sensor | ContextImageFileName = /prelude_dropper.exe/",
369
369
  test_id=pytest.test_id,
370
370
  )
371
371
 
@@ -394,13 +394,13 @@ class TestThreatHunt:
394
394
  pytest.expected_threat_hunt = unwrap(self.build.update_threat_hunt)(
395
395
  self.build,
396
396
  name="updated threat hunt",
397
- query='#repo=base_sensor | FilePath = "the-file-path"',
397
+ query="#repo=base_sensor | FilePath = /Prelude Security/ | groupBy([@timestamp, ParentBaseFileName, ImageFileName, aid], limit=20)| sort(@timestamp, limit=20)",
398
398
  threat_hunt_id=pytest.crwd_threat_hunt_id,
399
399
  )
400
400
  assert pytest.expected_threat_hunt["name"] == "updated threat hunt"
401
401
  assert (
402
402
  pytest.expected_threat_hunt["query"]
403
- == '#repo=base_sensor | FilePath = "the-file-path"'
403
+ == "#repo=base_sensor | FilePath = /Prelude Security/ | groupBy([@timestamp, ParentBaseFileName, ImageFileName, aid], limit=20)| sort(@timestamp, limit=20)"
404
404
  )
405
405
 
406
406
  @pytest.mark.order(-7)
@@ -74,7 +74,7 @@ class TestIAM:
74
74
  unwrap(self.iam_user.update_user)(self.iam_user, name="Robb")
75
75
 
76
76
  for user in pytest.expected_account["users"]:
77
- if user["handle"] == pytest.account.handle:
77
+ if user["handle"] == pytest.account.handle and not user["oidc"]:
78
78
  user["name"] = "Robb"
79
79
  break
80
80
 
@@ -100,7 +100,7 @@ class TestPartnerAttach:
100
100
  ),
101
101
  )
102
102
  for c in Control
103
- if c.value > 0
103
+ if c.value > 0 and not c.parent
104
104
  ]
105
105
 
106
106
  def setup_class(self):
@@ -431,11 +431,8 @@ class TestPartner:
431
431
  )
432
432
  assert 1 == len(res)
433
433
  expected = dict(
434
- blocked=0,
435
- detected=0,
436
434
  detection_id=pytest.detection_id,
437
435
  group_id=group_id,
438
- monitored=0,
439
436
  platform=pytest.expected_detection["rule"]["logsource"]["product"],
440
437
  test_id=pytest.test_id,
441
438
  )
@@ -471,32 +468,6 @@ class TestPartner:
471
468
  res[control.name][0]["account_id"] == pytest.expected_account["account_id"]
472
469
  )
473
470
 
474
- def test_ioa_stats(
475
- self,
476
- unwrap,
477
- host,
478
- edr_id,
479
- control,
480
- os,
481
- platform,
482
- policy,
483
- policy_name,
484
- webhook_keys,
485
- group_id,
486
- ):
487
- try:
488
- if control != Control.CROWDSTRIKE:
489
- pytest.skip("IOA stats only supported for CROWDSTRIKE")
490
- if not pytest.expected_account["features"]["observed_detected"]:
491
- pytest.skip("OBSERVED_DETECTED feature not enabled")
492
-
493
- res = unwrap(self.partner.ioa_stats)(self.partner)
494
- assert 0 == len(res)
495
- finally:
496
- unwrap(self.detect.delete_endpoint)(
497
- self.detect, ident=pytest.endpoint["endpoint_id"]
498
- )
499
-
500
471
  def test_list_advisories(
501
472
  self,
502
473
  unwrap,
@@ -33,7 +33,7 @@ class TestScmAcrossControls:
33
33
  unwrap(self.scm.upsert_notification)(
34
34
  self.scm,
35
35
  ControlCategory.XDR,
36
- PartnerEvents.NO_EDR,
36
+ PartnerEvents.MISSING_EDR,
37
37
  RunCode.DAILY,
38
38
  0,
39
39
  ["test@email.com"],
@@ -43,7 +43,7 @@ class TestScmAcrossControls:
43
43
  for notification in notifications:
44
44
  if notification["id"] == self.notification_id:
45
45
  assert notification["scheduled_hour"] == 0
46
- assert notification["event"] == PartnerEvents.NO_EDR.value
46
+ assert notification["event"] == PartnerEvents.MISSING_EDR.value
47
47
 
48
48
  def test_update_notification(self, unwrap):
49
49
  unwrap(self.scm.upsert_notification)(
@@ -89,158 +89,13 @@ class TestScmAcrossControls:
89
89
  _compare_keys(nested_expected, nested_actual)
90
90
 
91
91
  summary = unwrap(self.scm.evaluation_summary)(self.scm)
92
- expected = {
93
- "inbox_summary": {
94
- "categories": [
95
- {
96
- "category": None,
97
- "inbox_count": None,
98
- "excepted": {
99
- "inbox_count": None,
100
- },
101
- "instances": [
102
- {
103
- "control": None,
104
- "inbox_count": None,
105
- "instance_id": None,
106
- "setting_count": None,
107
- "setting_misconfiguration_count": None,
108
- "excepted": {
109
- "inbox_count": None,
110
- "setting_misconfiguration_count": None,
111
- },
112
- }
113
- ],
114
- }
115
- ],
116
- "inbox_count": None,
117
- "excepted": {
118
- "inbox_count": None,
119
- },
120
- },
121
- "user_summary": {
122
- "categories": [
123
- {
124
- "category": None,
125
- "control_failure_count": None,
126
- "any_endpoint_failure_count": None,
127
- "all_endpoint_failure_count": None,
128
- "user_count": None,
129
- "excepted": {
130
- "control_failure_count": None,
131
- "any_endpoint_failure_count": None,
132
- "all_endpoint_failure_count": None,
133
- "user_count": None,
134
- },
135
- "instances": [
136
- {
137
- "control": None,
138
- "instance_id": None,
139
- "control_failure_count": None,
140
- "missing_mfa_count": None,
141
- "user_count": None,
142
- "missing_asset_manager_count": None,
143
- "missing_edr_count": None,
144
- "missing_vuln_manager_count": None,
145
- "any_endpoint_failure_count": None,
146
- "all_endpoint_failure_count": None,
147
- "setting_count": None,
148
- "setting_misconfiguration_count": None,
149
- "excepted": {
150
- "control_failure_count": None,
151
- "missing_mfa_count": None,
152
- "user_count": None,
153
- "missing_asset_manager_count": None,
154
- "missing_edr_count": None,
155
- "missing_vuln_manager_count": None,
156
- "any_endpoint_failure_count": None,
157
- "all_endpoint_failure_count": None,
158
- "setting_misconfiguration_count": None,
159
- },
160
- },
161
- ],
162
- }
163
- ],
164
- "user_count": None,
165
- "control_failure_count": None,
166
- "any_endpoint_failure_count": None,
167
- "all_endpoint_failure_count": None,
168
- "excepted": {
169
- "user_count": None,
170
- "control_failure_count": None,
171
- "any_endpoint_failure_count": None,
172
- "all_endpoint_failure_count": None,
173
- },
174
- },
175
- "endpoint_summary": {
176
- "categories": [
177
- {
178
- "category": None,
179
- "control_failure_count": None,
180
- "endpoint_count": None,
181
- "missing_asset_manager_count": None,
182
- "missing_server_manager_count": None,
183
- "missing_edr_count": None,
184
- "missing_vuln_manager_count": None,
185
- "missing_vuln_scan_count": None,
186
- "excepted": {
187
- "control_failure_count": None,
188
- "endpoint_count": None,
189
- "missing_asset_manager_count": None,
190
- "missing_server_manager_count": None,
191
- "missing_edr_count": None,
192
- "missing_vuln_manager_count": None,
193
- "missing_vuln_scan_count": None,
194
- },
195
- "instances": [
196
- {
197
- "control": None,
198
- "instance_id": None,
199
- "control_failure_count": None,
200
- "endpoint_count": None,
201
- "no_av_policy": None,
202
- "no_edr_policy": None,
203
- "policy_conflict_count": None,
204
- "reduced_functionality_mode": None,
205
- "missing_agent_count": None,
206
- "missing_scan_count": None,
207
- "out_of_date_scan_count": None,
208
- "setting_count": None,
209
- "setting_misconfiguration_count": None,
210
- "excepted": {
211
- "control_failure_count": None,
212
- "endpoint_count": None,
213
- "no_av_policy": None,
214
- "no_edr_policy": None,
215
- "reduced_functionality_mode": None,
216
- "missing_agent_count": None,
217
- "missing_scan_count": None,
218
- "out_of_date_scan_count": None,
219
- "setting_misconfiguration_count": None,
220
- },
221
- }
222
- ],
223
- },
224
- ],
225
- "endpoint_count": None,
226
- "missing_asset_manager_count": None,
227
- "missing_server_manager_count": None,
228
- "missing_edr_count": None,
229
- "missing_vuln_manager_count": None,
230
- "missing_vuln_scan_count": None,
231
- "excepted": {
232
- "endpoint_count": None,
233
- "missing_asset_manager_count": None,
234
- "missing_server_manager_count": None,
235
- "missing_edr_count": None,
236
- "missing_vuln_manager_count": None,
237
- "missing_vuln_scan_count": None,
238
- },
239
- },
92
+ assert summary.keys() == {
93
+ "endpoint_summary",
94
+ "inbox_summary",
95
+ "network_device_summary",
96
+ "user_summary",
240
97
  }
241
98
 
242
- _compare_keys(expected, summary)
243
-
244
99
  def test_technique_summary(self, unwrap):
245
100
  summary = unwrap(self.scm.technique_summary)(self.scm, "T1078,T1027")
246
101
  assert len(summary) > 0
@@ -261,7 +116,7 @@ class TestScmAcrossControls:
261
116
  job_id = unwrap(self.export.export_scm)(
262
117
  self.export,
263
118
  SCMCategory.ENDPOINT,
264
- filter="contains(normalized_hostname, 'spencer')",
119
+ filter="contains(hostname, 'spencer')",
265
120
  top=1,
266
121
  )["job_id"]
267
122
  while (result := unwrap(self.jobs.job_status)(self.jobs, job_id))[
@@ -276,7 +131,8 @@ class TestScmAcrossControls:
276
131
  @pytest.mark.order(9)
277
132
  @pytest.mark.usefixtures("setup_account")
278
133
  @pytest.mark.parametrize(
279
- "control", [c for c in Control if c.scm_category != SCMCategory.NONE]
134
+ "control",
135
+ [c for c in Control if c.scm_category != SCMCategory.NONE and not c.parent],
280
136
  )
281
137
  class TestScmPerControl:
282
138
  def setup_class(self):
@@ -313,15 +169,29 @@ class TestScmPerControl:
313
169
  raise e
314
170
 
315
171
  def test_evaluation(self, unwrap, control):
172
+ def _wait_for_policies(control, instance_id, category):
173
+ timeout = time.time() + 300
174
+ while time.time() < timeout:
175
+ evaluation = unwrap(self.scm.evaluation)(self.scm, control, instance_id)
176
+ evaluation = evaluation[f"{category}_evaluation"]
177
+ if len(evaluation["policies"]) > 0:
178
+ return evaluation
179
+ time.sleep(5)
180
+ assert False, "Timed out waiting for policies to show up in evaluation"
181
+
316
182
  instance_id = pytest.controls.get(control.value)
317
183
  assert instance_id
318
184
  evaluation = unwrap(self.scm.evaluation)(self.scm, control, instance_id)
319
185
  if "endpoint_evaluation" in evaluation:
320
186
  evaluation = evaluation["endpoint_evaluation"]
321
187
  assert {"policies"} == evaluation.keys()
322
- if control.control_category == ControlCategory.XDR:
323
- assert len(evaluation["policies"]) > 0
188
+ if (
189
+ control.control_category == ControlCategory.XDR
190
+ or control == Control.INTUNE
191
+ ):
192
+ evaluation = _wait_for_policies(control, instance_id, "endpoint")
324
193
  assert {
194
+ "excepted",
325
195
  "id",
326
196
  "name",
327
197
  "platform",
@@ -335,21 +205,17 @@ class TestScmPerControl:
335
205
  elif "user_evaluation" in evaluation:
336
206
  evaluation = evaluation["user_evaluation"]
337
207
  assert {"policies"} == evaluation.keys()
338
- assert len(evaluation["policies"]) > 0
339
- assert {
340
- "id",
341
- "name",
342
- "noncompliant_hostnames",
343
- "settings",
344
- "user_count",
345
- } == evaluation["policies"][0].keys()
208
+ evaluation = _wait_for_policies(control, instance_id, "user")
209
+ assert {"excepted", "id", "name", "settings", "user_count"} == evaluation[
210
+ "policies"
211
+ ][0].keys()
346
212
  elif "inbox_evaluation" in evaluation:
347
213
  evaluation = evaluation["inbox_evaluation"]
348
214
  assert {"policies"} == evaluation.keys()
349
- assert len(evaluation["policies"]) > 0
350
- assert {"id", "name", "settings", "inbox_count"} == evaluation["policies"][
351
- 0
352
- ].keys()
215
+ evaluation = _wait_for_policies(control, instance_id, "inbox")
216
+ assert {"excepted", "id", "name", "settings", "inbox_count"} == evaluation[
217
+ "policies"
218
+ ][0].keys()
353
219
  else:
354
220
  assert False, "No evaluation returned"
355
221
 
@@ -17,7 +17,7 @@ class TestScmBuild:
17
17
  res = unwrap(self.scm.create_object_exception)(
18
18
  self.scm,
19
19
  ControlCategory.ASSET_MANAGER,
20
- "normalized_hostname eq 'host1'",
20
+ "hostname eq 'host1'",
21
21
  name="filter me",
22
22
  expires="5555-05-05",
23
23
  )
@@ -28,7 +28,7 @@ class TestScmBuild:
28
28
  res = unwrap(self.scm.update_object_exception)(
29
29
  self.scm,
30
30
  pytest.exception_id,
31
- filter="normalized_hostname eq 'host2'",
31
+ filter="hostname eq 'host2'",
32
32
  expires=None,
33
33
  )
34
34
  assert res["status"]
@@ -43,7 +43,7 @@ class TestScmBuild:
43
43
  assert exception == {
44
44
  "category": ControlCategory.ASSET_MANAGER.value,
45
45
  "expires": None,
46
- "filter": "normalized_hostname eq 'host2'",
46
+ "filter": "hostname eq 'host2'",
47
47
  "id": pytest.exception_id,
48
48
  "name": "filter me",
49
49
  }
File without changes