praetorian-cli 2.2.3__tar.gz → 2.2.4__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.
Files changed (109) hide show
  1. {praetorian_cli-2.2.3/praetorian_cli.egg-info → praetorian_cli-2.2.4}/PKG-INFO +1 -1
  2. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/handlers/add.py +22 -0
  3. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/handlers/cli_decorators.py +1 -1
  4. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/handlers/delete.py +12 -0
  5. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/handlers/get.py +17 -0
  6. praetorian_cli-2.2.4/praetorian_cli/handlers/link.py +60 -0
  7. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/handlers/list.py +22 -0
  8. praetorian_cli-2.2.4/praetorian_cli/handlers/unlink.py +55 -0
  9. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/sdk/chariot.py +2 -0
  10. praetorian_cli-2.2.4/praetorian_cli/sdk/entities/webpage.py +180 -0
  11. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/sdk/model/globals.py +2 -0
  12. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/sdk/model/query.py +7 -0
  13. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/sdk/test/test_asset.py +36 -0
  14. praetorian_cli-2.2.4/praetorian_cli/sdk/test/test_webpage.py +46 -0
  15. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/sdk/test/test_z_cli.py +31 -0
  16. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/sdk/test/utils.py +5 -0
  17. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4/praetorian_cli.egg-info}/PKG-INFO +1 -1
  18. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli.egg-info/SOURCES.txt +2 -0
  19. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/setup.cfg +1 -1
  20. praetorian_cli-2.2.3/praetorian_cli/handlers/link.py +0 -32
  21. praetorian_cli-2.2.3/praetorian_cli/handlers/unlink.py +0 -27
  22. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/LICENSE +0 -0
  23. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/MANIFEST.in +0 -0
  24. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/README.md +0 -0
  25. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/__init__.py +0 -0
  26. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/handlers/__init__.py +0 -0
  27. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/handlers/aegis.py +0 -0
  28. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/handlers/agent.py +0 -0
  29. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/handlers/chariot.py +0 -0
  30. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/handlers/configure.py +0 -0
  31. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/handlers/enrich.py +0 -0
  32. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/handlers/imports.py +0 -0
  33. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/handlers/script.py +0 -0
  34. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/handlers/search.py +0 -0
  35. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/handlers/ssh_utils.py +0 -0
  36. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/handlers/test.py +0 -0
  37. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/handlers/update.py +0 -0
  38. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/handlers/utils.py +0 -0
  39. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/main.py +0 -0
  40. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/scripts/__init__.py +0 -0
  41. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/scripts/commands/__init__.py +0 -0
  42. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/scripts/commands/nmap-example.py +0 -0
  43. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/scripts/utils.py +0 -0
  44. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/sdk/__init__.py +0 -0
  45. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/sdk/entities/__init__.py +0 -0
  46. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/sdk/entities/accounts.py +0 -0
  47. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/sdk/entities/aegis.py +0 -0
  48. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/sdk/entities/agents.py +0 -0
  49. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/sdk/entities/assets.py +0 -0
  50. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/sdk/entities/attributes.py +0 -0
  51. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/sdk/entities/capabilities.py +0 -0
  52. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/sdk/entities/configurations.py +0 -0
  53. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/sdk/entities/credentials.py +0 -0
  54. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/sdk/entities/definitions.py +0 -0
  55. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/sdk/entities/files.py +0 -0
  56. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/sdk/entities/integrations.py +0 -0
  57. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/sdk/entities/jobs.py +0 -0
  58. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/sdk/entities/keys.py +0 -0
  59. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/sdk/entities/preseeds.py +0 -0
  60. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/sdk/entities/risks.py +0 -0
  61. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/sdk/entities/scanners.py +0 -0
  62. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/sdk/entities/schema.py +0 -0
  63. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/sdk/entities/search.py +0 -0
  64. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/sdk/entities/seeds.py +0 -0
  65. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/sdk/entities/settings.py +0 -0
  66. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/sdk/entities/statistics.py +0 -0
  67. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/sdk/entities/webhook.py +0 -0
  68. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/sdk/keychain.py +0 -0
  69. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/sdk/mcp_server.py +0 -0
  70. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/sdk/model/__init__.py +0 -0
  71. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/sdk/model/aegis.py +0 -0
  72. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/sdk/model/utils.py +0 -0
  73. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/sdk/test/__init__.py +0 -0
  74. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/sdk/test/pytest.ini +0 -0
  75. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/sdk/test/test_account.py +0 -0
  76. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/sdk/test/test_agent.py +0 -0
  77. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/sdk/test/test_attribute.py +0 -0
  78. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/sdk/test/test_capabilities.py +0 -0
  79. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/sdk/test/test_configuration.py +0 -0
  80. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/sdk/test/test_definition.py +0 -0
  81. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/sdk/test/test_extend.py +0 -0
  82. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/sdk/test/test_file.py +0 -0
  83. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/sdk/test/test_job.py +0 -0
  84. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/sdk/test/test_key.py +0 -0
  85. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/sdk/test/test_mcp.py +0 -0
  86. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/sdk/test/test_preseed.py +0 -0
  87. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/sdk/test/test_risk.py +0 -0
  88. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/sdk/test/test_search.py +0 -0
  89. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/sdk/test/test_seed.py +0 -0
  90. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/sdk/test/test_setting.py +0 -0
  91. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/sdk/test/test_webhook.py +0 -0
  92. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/sdk/test/ui_mocks.py +0 -0
  93. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/ui/__init__.py +0 -0
  94. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/ui/aegis/__init__.py +0 -0
  95. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/ui/aegis/commands/__init__.py +0 -0
  96. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/ui/aegis/commands/help.py +0 -0
  97. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/ui/aegis/commands/info.py +0 -0
  98. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/ui/aegis/commands/job.py +0 -0
  99. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/ui/aegis/commands/list.py +0 -0
  100. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/ui/aegis/commands/set.py +0 -0
  101. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/ui/aegis/commands/ssh.py +0 -0
  102. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/ui/aegis/constants.py +0 -0
  103. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/ui/aegis/menu.py +0 -0
  104. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli/ui/aegis/utils.py +0 -0
  105. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli.egg-info/dependency_links.txt +0 -0
  106. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli.egg-info/entry_points.txt +0 -0
  107. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli.egg-info/requires.txt +0 -0
  108. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/praetorian_cli.egg-info/top_level.txt +0 -0
  109. {praetorian_cli-2.2.3 → praetorian_cli-2.2.4}/pyproject.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: praetorian-cli
3
- Version: 2.2.3
3
+ Version: 2.2.4
4
4
  Summary: For interacting with the Chariot API
5
5
  Home-page: https://github.com/praetorian-inc/praetorian-cli
6
6
  Author: Praetorian
@@ -29,6 +29,9 @@ def asset(sdk, name, dns, asset_type, status, surface):
29
29
  Add an asset to the Chariot database. This command requires a DNS name for the asset.
30
30
  Optionally, a name can be provided to give the asset more specific information,
31
31
  such as IP address. If no name is provided, the DNS name will be used as the name.
32
+ The DNS is the group and the name is the specific identifier. This is for legacy reasons.
33
+
34
+ The type can be one of the following: asset, addomain, repository, webapplication.
32
35
 
33
36
  \b
34
37
  Example assets:
@@ -41,6 +44,7 @@ def asset(sdk, name, dns, asset_type, status, surface):
41
44
  - praetorian chariot add asset --dns example.com
42
45
  - praetorian chariot add asset --dns example.com --name 1.2.3.4
43
46
  - praetorian chariot add asset --dns internal.example.com --name 10.2.3.4 --surface internal
47
+ - praetorian chariot add asset --dns https://example.com --name 'Example Web Application' --type webapplication
44
48
  """
45
49
  if not name:
46
50
  name = dns
@@ -328,3 +332,21 @@ def key(sdk, name, expires):
328
332
  return
329
333
  click.echo(f'API key created: {result.get("key", "N/A")}')
330
334
  click.echo(f'Secret (save this, it will not be shown again): {result["secret"]}')
335
+
336
+
337
+ @add.command()
338
+ @cli_handler
339
+ @click.option('-u', '--url', required=True, help='The full URL of the page')
340
+ @click.option('-p', '--parent', required=False, help='Optional key of the parent WebApplication')
341
+ def webpage(sdk, url, parent):
342
+ """ Add a Webpage
343
+
344
+ Add a web page to the Chariot database. Webpages can optionally be associated
345
+ with a parent WebApplication or exist independently.
346
+
347
+ \b
348
+ Example usages:
349
+ - praetorian chariot add webpage --url https://app.example.com/login
350
+ - praetorian chariot add webpage --url https://app.example.com/admin --parent "#webapplication#https://app.example.com"
351
+ """
352
+ sdk.webpage.add(url, parent)
@@ -68,7 +68,7 @@ def list_params(filter_by, has_details=True, has_filter=True, has_type=False):
68
68
 
69
69
 
70
70
  def pagination(func):
71
- func = click.option('-o', '--offset', default='', help='List results from an offset')(func)
71
+ func = click.option('-o', '--offset', default=0, help='List results from an offset')(func)
72
72
  func = click.option('-p', '--page', type=click.Choice(('first', 'all')), default='first',
73
73
  help='Pagination mode. "all" pages up to 10,000 pages.', show_default=True)(func)
74
74
  return func
@@ -182,3 +182,15 @@ def key(chariot, key):
182
182
  - praetorian chariot delete key "#key#550e8400-e29b-41d4-a716-446655440000"
183
183
  """
184
184
  chariot.keys.delete(key)
185
+
186
+ @delete.command()
187
+ @cli_handler
188
+ @click.argument('key', required=True)
189
+ def webpage(chariot, key):
190
+ """ Delete a webpage
191
+
192
+ \b
193
+ Arguments:
194
+ - KEY: the key of an existing webpage
195
+ """
196
+ chariot.webpage.delete(key)
@@ -300,6 +300,23 @@ def scanner(chariot, key):
300
300
 
301
301
  @get.command()
302
302
  @cli_handler
303
+ @click.argument('key', required=True)
304
+ def webpage(chariot, key):
305
+ """ Get Webpage details
306
+
307
+ Retrieve detailed information about a specific web page, including
308
+ its URL, method, authentication requirements, and other metadata.
309
+
310
+ \b
311
+ Argument:
312
+ - KEY: the key of an existing Webpage
313
+
314
+ \b
315
+ Example usages:
316
+ - praetorian chariot get webpage "#webpage#https://app.example.com/dashboard"
317
+ """
318
+ print_json(chariot.webpage.get(key))
319
+
303
320
  @click.option('-t', '--type', help='Optional specific entity type (e.g., asset, risk, attribute)')
304
321
  @click.option('-d', '--details', is_flag=True, help='Further retrieve the details of the schema')
305
322
  def schema(chariot, type, details):
@@ -0,0 +1,60 @@
1
+ import click
2
+
3
+ from praetorian_cli.handlers.chariot import chariot
4
+ from praetorian_cli.handlers.cli_decorators import cli_handler
5
+
6
+
7
+ @chariot.group()
8
+ def link():
9
+ """ Link resources to other entities """
10
+ pass
11
+
12
+
13
+ @link.command()
14
+ @cli_handler
15
+ @click.argument('username')
16
+ def account(chariot, username):
17
+ """ Add a collaborator account to your account
18
+
19
+ This allows them to assume access into your account
20
+ and perform actions on your behalf.
21
+
22
+ \b
23
+ Arguments:
24
+ - NAME: their email address
25
+
26
+
27
+
28
+ \b
29
+ Example usages:
30
+ - praetorian chariot link account john@praetorian.com
31
+ """
32
+ chariot.accounts.add_collaborator(username)
33
+
34
+
35
+ @link.command('webpage-source')
36
+ @cli_handler
37
+ @click.argument('webpage_key')
38
+ @click.argument('entity_key')
39
+ def webpage_source(chariot, webpage_key, entity_key):
40
+ """ Link a file or repository to a webpage as source code
41
+
42
+ This associates source code files or repositories with webpages
43
+ to track where webpage content originates from.
44
+
45
+ \b
46
+ Arguments:
47
+ - WEBPAGE_KEY: The webpage key in format #webpage#{url}
48
+ - ENTITY_KEY: The file or repository key to link
49
+ Format: #file#{path} or #repository#{url}#{name}
50
+
51
+ \b
52
+ Example usages:
53
+ - praetorian chariot link webpage-source "#webpage#https://example.com" "#file#proofs/scan.txt"
54
+ - praetorian chariot link webpage-source "#webpage#https://example.com/login" "#repository#https://github.com/org/repo.git#repo.git"
55
+ """
56
+ result = chariot.webpage.link_source(webpage_key, entity_key)
57
+ if result:
58
+ click.echo(f"Successfully linked {entity_key} to {webpage_key}")
59
+ if 'artifacts' in result:
60
+ click.echo(f"Webpage now has {len(result['artifacts'])} linked artifacts")
@@ -368,3 +368,25 @@ def scanners(chariot, filter, details, offset, page):
368
368
  - praetorian chariot list scanners --page all
369
369
  """
370
370
  render_list_results(chariot.scanners.list(filter, offset, pagination_size(page)), details)
371
+
372
+
373
+ @list.command()
374
+ @click.option('--parent', required=False, help='Optional WebApp key to filter pages')
375
+ @click.option('-f', '--filter', required=False, help='Optional URL to filter pages')
376
+ @click.option('-d', '--details', is_flag=True, default=False, help='Show detailed information')
377
+ @pagination
378
+ @cli_handler
379
+ def webpages(chariot, parent, filter, details, offset, page):
380
+ """ List WebPages
381
+
382
+ Retrieve and display a list of pages/URLs. Can optionally filter by specific WebApplication.
383
+
384
+ \b
385
+ Example usages:
386
+ - praetorian chariot list webpages
387
+ - praetorian chariot list webpages --parent "#webapplication#https://app.example.com"
388
+ - praetorian chariot list webpages --filter /login
389
+ - praetorian chariot list webpages --parent "#webapplication#https://app.example.com" --details
390
+ - praetorian chariot list webpages --page all
391
+ """
392
+ render_list_results(chariot.webpage.list(parent, filter, offset, pagination_size(page)), details)
@@ -0,0 +1,55 @@
1
+ import click
2
+
3
+ from praetorian_cli.handlers.chariot import chariot
4
+ from praetorian_cli.handlers.cli_decorators import cli_handler
5
+
6
+
7
+ @chariot.group()
8
+ def unlink():
9
+ """ Remove links between resources """
10
+ pass
11
+
12
+
13
+ @unlink.command()
14
+ @cli_handler
15
+ @click.argument('username')
16
+ def account(chariot, username):
17
+ """ Remove a collaborator account from your account. This will
18
+ revoke their access to your account.
19
+
20
+ Arguments:
21
+ - NAME: Their email address.
22
+
23
+ \b
24
+ Example usages:
25
+ - praetorian chariot unlink account john@praetorian.com
26
+ """
27
+ chariot.accounts.delete_collaborator(username)
28
+
29
+
30
+ @unlink.command('webpage-source')
31
+ @cli_handler
32
+ @click.argument('webpage_key')
33
+ @click.argument('entity_key')
34
+ def webpage_source(chariot, webpage_key, entity_key):
35
+ """ Unlink a file or repository from a webpage's source code
36
+
37
+ This removes the association between source code files or
38
+ repositories and webpages.
39
+
40
+ \b
41
+ Arguments:
42
+ - WEBPAGE_KEY: The webpage key in format #webpage#{url}
43
+ - ENTITY_KEY: The file or repository key to unlink
44
+ Format: #file#{path} or #repository#{url}#{name}
45
+
46
+ \b
47
+ Example usages:
48
+ - praetorian chariot unlink webpage-source "#webpage#https://example.com" "#file#proofs/scan.txt"
49
+ - praetorian chariot unlink webpage-source "#webpage#https://example.com/login" "#repository#https://github.com/org/repo.git#repo.git"
50
+ """
51
+ result = chariot.webpage.unlink_source(webpage_key, entity_key)
52
+ if result:
53
+ click.echo(f"Successfully unlinked {entity_key} from {webpage_key}")
54
+ if 'artifacts' in result:
55
+ click.echo(f"Webpage now has {len(result['artifacts'])} linked artifacts")
@@ -21,6 +21,7 @@ from praetorian_cli.sdk.entities.search import Search
21
21
  from praetorian_cli.sdk.entities.seeds import Seeds
22
22
  from praetorian_cli.sdk.entities.settings import Settings
23
23
  from praetorian_cli.sdk.entities.statistics import Statistics
24
+ from praetorian_cli.sdk.entities.webpage import Webpage
24
25
  from praetorian_cli.sdk.entities.webhook import Webhook
25
26
  from praetorian_cli.sdk.keychain import Keychain
26
27
  from praetorian_cli.sdk.model.globals import GLOBAL_FLAG
@@ -52,6 +53,7 @@ class Chariot:
52
53
  self.keys = Keys(self)
53
54
  self.capabilities = Capabilities(self)
54
55
  self.credentials = Credentials(self)
56
+ self.webpage = Webpage(self)
55
57
  self.schema = Schema(self)
56
58
  self.proxy = proxy
57
59
 
@@ -0,0 +1,180 @@
1
+ from praetorian_cli.sdk.model.query import Query, Node, Filter, Relationship
2
+
3
+
4
+ class Webpage:
5
+ """The methods in this class are to be accessed from sdk.webpage, where sdk
6
+ is an instance of Chariot."""
7
+
8
+ def __init__(self, api):
9
+ self.api = api
10
+
11
+ def add(self, url, parent_key=None):
12
+ """
13
+ Add a Webpage to the Chariot database.
14
+
15
+ WebPages represent individual pages or endpoints that can be optionally
16
+ associated with a parent WebApplication. The backend uses a WebpageRequest
17
+ structure with embedded webpage data and optional parent key.
18
+
19
+ :param url: The full URL of the page
20
+ :type url: str
21
+ :param parent_key: Optional key of the parent WebApplication
22
+ :type webapp_key: str or None
23
+ :return: The created WebPage object
24
+ :rtype: dict
25
+ :raises Exception: If the URL is invalid or the request fails
26
+
27
+ **Example Usage:**
28
+ >>> # Add a simple page without parent
29
+ >>> page = sdk.webpage.add("https://app.example.com/login")
30
+
31
+ >>> # Add a page with parent WebApplication
32
+ >>> page = sdk.webpage.add(
33
+ ... url="https://app.example.com/admin",
34
+ ... parent_key="#webapplication#https://app.example.com")
35
+
36
+ **WebPage Object Structure:**
37
+ The returned Webpage object contains:
38
+ - key: Webpage identifier in format #webpage#{url}
39
+ - url: Full URL of the page
40
+ - status: Current status
41
+ - parent: Parent WebApplication relationship (if applicable)
42
+ - created: Creation timestamp
43
+ """
44
+ if not url:
45
+ raise Exception("URL is required for Webpage")
46
+
47
+ if parent_key and not parent_key.startswith('#webapplication#'):
48
+ raise Exception("Invalid WebApplication key format")
49
+
50
+ payload = {
51
+ 'webpage': {
52
+ 'url': url,
53
+ 'status': 'A' # Active status
54
+ }
55
+
56
+ }
57
+
58
+ if parent_key:
59
+ payload['parent_key'] = parent_key
60
+
61
+ return self.api.post('webpage', payload)
62
+
63
+ def get(self, key):
64
+ """
65
+ Get details of a specific Webpage by its key.
66
+
67
+ :param key: The WebPage key identifier
68
+ :type key: str
69
+ :return: Webpage object with detailed information, or None if not found
70
+ :rtype: dict or None
71
+
72
+ **Example Usage:**
73
+ >>> # Get a specific Webpage
74
+ >>> page = sdk.webpage.get("webpage_key_123")
75
+
76
+ **Webpage Object Structure:**
77
+ The returned Webpage object contains:
78
+ - key: Webpage identifier
79
+ - url: Full URL of the page
80
+ - created: Creation timestamp
81
+ - updated: Last update timestamp
82
+ """
83
+ query = Query(node=Node(labels=[Node.Label.WEBPAGE], filters=[Filter(field=Filter.Field.KEY, operator=Filter.Operator.EQUAL, value=key)]))
84
+ return self.api.search.by_query(query)[0][0]
85
+
86
+ def list(self, parent_key=None, filter=None, offset=0, pages=100000) -> tuple:
87
+ """
88
+ List Webpages, optionally filtered by parent WebApplication.
89
+
90
+ Retrieve Webpage entities with optional filtering capabilities. Can filter by
91
+ parent WebApplication.
92
+
93
+ :param parent_key: Filter pages by specific WebApplication (optional)
94
+ :type parent_key: str or None
95
+ :param filter: Filter pages by specific URL (optional)
96
+ :type filter: str or None
97
+ :param offset: The offset for pagination to retrieve a specific page of results
98
+ :type offset: str or None
99
+ :param pages: Maximum number of pages to retrieve (default: 100000 for all results)
100
+ :type pages: int
101
+ :return: A tuple containing (list of matching Webpages, next page offset)
102
+ :rtype: tuple
103
+
104
+ **Example Usage:**
105
+ >>> # List all WebPages
106
+ >>> pages, offset = sdk.webpage.list()
107
+
108
+ >>> # List pages for specific WebApplication
109
+ >>> pages, offset = sdk.webpage.list(
110
+ ... webapp_key="#asset#webapp#https://app.example.com#https://app.example.com")
111
+
112
+ **WebPage Filtering:**
113
+ - parent_key: Filters by parent WebApplication
114
+ - filter: Filters by specific URL
115
+ """
116
+ if parent_key and not parent_key.startswith('#webapplication#'):
117
+ raise Exception("Invalid WebApplication key format")
118
+
119
+ relationships = []
120
+ filters = []
121
+ if parent_key:
122
+ parentFilter = Filter(field=Filter.Field.KEY, operator=Filter.Operator.EQUAL, value=parent_key)
123
+ relationship = Relationship(label=Relationship.Label.HAS_WEBPAGE, target=Node(labels=[Node.Label.WEBAPPLICATION], filters=[parentFilter]))
124
+ relationships.append(relationship)
125
+ if filter:
126
+ urlFilter = Filter(field=Filter.Field.KEY, operator=Filter.Operator.CONTAINS, value=filter)
127
+ filters.append(urlFilter)
128
+ node = Node(labels=[Node.Label.WEBPAGE], filters=filters, relationships=relationships)
129
+ query = Query(node=node, page=offset, limit=pages)
130
+ return self.api.search.by_query(query, pages)
131
+
132
+ def delete(self, key):
133
+ """
134
+ Delete a webpage by its key.
135
+
136
+ :param key: The WebPage key identifier
137
+ :type key: str
138
+ """
139
+ body = {
140
+ 'webpage': {
141
+ 'key': key
142
+ }
143
+ }
144
+ self.api.delete('webpage', params={}, body=body)
145
+
146
+ def link_source(self, webpage_key, entity_key):
147
+ """
148
+ Link a file or repository to a webpage as source code.
149
+
150
+ :param webpage_key: The webpage key in format #webpage#{url}
151
+ :type webpage_key: str
152
+ :param entity_key: The entity key (file or repository) to link. Format: #file#{path} or #repository#{url}#{name}
153
+ :type entity_key: str
154
+ :return: The updated webpage with linked artifacts
155
+ :rtype: dict
156
+ """
157
+ data = {
158
+ 'webpageKey': webpage_key,
159
+ 'entityKey': entity_key
160
+ }
161
+
162
+ return self.api.put('webpage/link', data, {})
163
+
164
+ def unlink_source(self, webpage_key, entity_key):
165
+ """
166
+ Unlink a file or repository from a webpage's source code.
167
+
168
+ :param webpage_key: The webpage key in format #webpage#{url}
169
+ :type webpage_key: str
170
+ :param entity_key: The entity key (file or repository) to unlink. Format: #file#{path} or #repository#{url}#{name}
171
+ :type entity_key: str
172
+ :return: The updated webpage with artifacts removed
173
+ :rtype: dict
174
+ """
175
+ data = {
176
+ 'webpageKey': webpage_key,
177
+ 'entityKey': entity_key
178
+ }
179
+
180
+ return self.api.delete('webpage/link', data, {})
@@ -108,6 +108,8 @@ class Kind(Enum):
108
108
  SEED = 'seed'
109
109
  PRESEED = 'preseed'
110
110
  OTHERS = 'others'
111
+ WEBAPPLICATION = 'webapplication'
112
+ WEBPAGE = 'webpage'
111
113
 
112
114
  EXACT_FLAG = {'exact': 'true'}
113
115
  DESCENDING_FLAG = {'desc': 'true'}
@@ -49,6 +49,8 @@ class Filter:
49
49
  KEV = 'kev'
50
50
  EXPLOIT = 'exploit'
51
51
  PRIVATE = 'private'
52
+ PRIMARY_URL = 'primary_url'
53
+ URL = 'url'
52
54
 
53
55
  def __init__(self, field: Field, operator: Operator, value: str, not_: bool = False):
54
56
  self.field = field
@@ -65,6 +67,7 @@ class Relationship:
65
67
  HAS_VULNERABILITY = 'HAS_VULNERABILITY'
66
68
  DISCOVERED = 'DISCOVERED'
67
69
  HAS_ATTRIBUTE = 'HAS_ATTRIBUTE'
70
+ HAS_WEBPAGE = 'HAS_WEBPAGE'
68
71
 
69
72
  def __init__(self, label: Label, source: 'Node' = None, target: 'Node' = None, optional: bool = False, length: int = 0):
70
73
  self.label = label
@@ -97,6 +100,8 @@ class Node:
97
100
  PRESEED = 'Preseed'
98
101
  SEED = 'Seed'
99
102
  TTL = 'TTL'
103
+ WEBAPPLICATION = 'WebApplication'
104
+ WEBPAGE = 'Webpage'
100
105
 
101
106
  def __init__(self, labels: list[Label] = None, filters: list[Filter] = None,
102
107
  relationships: list[Relationship] = None):
@@ -157,6 +162,8 @@ KIND_TO_LABEL = {
157
162
  Kind.REPOSITORY.value: Node.Label.REPOSITORY,
158
163
  Kind.INTEGRATION.value: Node.Label.INTEGRATION,
159
164
  Kind.ADDOMAIN.value: Node.Label.ADDOMAIN,
165
+ Kind.WEBAPPLICATION.value: Node.Label.WEBAPPLICATION,
166
+ Kind.WEBPAGE.value: Node.Label.WEBPAGE,
160
167
  }
161
168
 
162
169
 
@@ -71,11 +71,47 @@ class TestAsset:
71
71
  deleted_assets, _ = self.sdk.search.by_status(Asset.DELETED.value, Kind.ADDOMAIN.value)
72
72
  assert any([a['key'] == self.ad_domain_key for a in deleted_assets])
73
73
 
74
+ def test_add_webapplication(self):
75
+ asset = self.sdk.assets.add(self.webapp_name, self.webapp_url, status=Asset.ACTIVE.value, surface='test-surface', type=Kind.WEBAPPLICATION.value)
76
+ assert asset['key'] == self.webapp_key
77
+ assert len(asset['attackSurface']) == 1
78
+ assert 'test-surface' in asset['attackSurface']
79
+ assert asset['status'] == Asset.ACTIVE.value
80
+
81
+ def test_get_webapplication(self):
82
+ asset = self.sdk.assets.get(self.webapp_key)
83
+ assert asset['key'] == self.webapp_key
84
+ assert asset['group'] == self.webapp_name
85
+ assert asset['identifier'] == self.webapp_url
86
+ assert asset['status'] == Asset.ACTIVE.value
87
+
88
+ def test_list_webapplication(self):
89
+ results, _ = self.sdk.assets.list(asset_type=Kind.WEBAPPLICATION.value)
90
+ assert len(results) > 0
91
+ assert any([a['key'] == self.webapp_key for a in results])
92
+ assert any([a['group'] == self.webapp_name for a in results])
93
+ assert any([a['identifier'] == self.webapp_url for a in results])
94
+
95
+ def test_update_webapplication(self):
96
+ self.sdk.assets.update(self.webapp_key, status=Asset.FROZEN.value, surface='abc')
97
+ asset = self.get_webapplication()
98
+ assert asset['status'] == Asset.FROZEN.value
99
+ assert 'abc' in asset['attackSurface']
100
+
101
+ def test_delete_webapplication(self):
102
+ self.sdk.assets.delete(self.webapp_key)
103
+ assert self.get_webapplication()['status'] == Asset.DELETED.value
104
+ deleted_assets, _ = self.sdk.search.by_status(Asset.DELETED.value, Kind.WEBAPPLICATION.value)
105
+ assert any([a['key'] == self.webapp_key for a in deleted_assets])
106
+
74
107
  def get_asset(self):
75
108
  return self.sdk.assets.get(self.asset_key)
76
109
 
77
110
  def get_ad_domain(self):
78
111
  return self.sdk.assets.get(self.ad_domain_key)
79
112
 
113
+ def get_webapplication(self):
114
+ return self.sdk.assets.get(self.webapp_key)
115
+
80
116
  def teardown_class(self):
81
117
  clean_test_entities(self.sdk, self)
@@ -0,0 +1,46 @@
1
+ import pytest
2
+
3
+ from praetorian_cli.sdk.test.utils import make_test_values, setup_chariot
4
+
5
+
6
+ @pytest.mark.coherence
7
+ class TestWebpage:
8
+ """Test suite for the Webpage entity class."""
9
+
10
+ def setup_class(self):
11
+ self.sdk = setup_chariot()
12
+ make_test_values(self)
13
+
14
+ def test_add_webpage(self):
15
+ """Test adding a Webpage with URL provided."""
16
+ result = self.sdk.webpage.add(self.webpage_url)
17
+
18
+ assert result is not None
19
+ webpage = result.get('webpages')[0]
20
+ assert webpage.get('key') == self.webpage_key
21
+ assert webpage.get('url') == self.webpage_url
22
+
23
+ def test_get_webpage(self):
24
+ """Test retrieving a Webpage by key."""
25
+ result = self.sdk.webpage.get(self.webpage_key)
26
+ assert result is not None
27
+ assert result.get('key') == self.webpage_key
28
+ assert result.get('url') == self.webpage_url
29
+
30
+ def test_list_webpages(self):
31
+ """Test listing Webpages."""
32
+ results, offset = self.sdk.webpage.list(filter=self.webpage_url[:len(self.webpage_url)//2])
33
+ assert isinstance(results, list)
34
+ assert len(results) > 0
35
+ assert any(r.get('key') == self.webpage_key for r in results)
36
+ assert any(r.get('url') == self.webpage_url for r in results)
37
+
38
+ def test_add_webpage_empty_url_raises_exception(self):
39
+ """Test that adding a Webpage with empty URL raises an exception."""
40
+ with pytest.raises(Exception, match="URL is required for Webpage"):
41
+ self.sdk.webpage.add("")
42
+
43
+ def test_add_webpage_none_url_raises_exception(self):
44
+ """Test that adding a Webpage with None URL raises an exception."""
45
+ with pytest.raises(Exception, match="URL is required for Webpage"):
46
+ self.sdk.webpage.add(None)
@@ -210,6 +210,21 @@ class TestZCli:
210
210
  self.verify(f'list accounts -f {o.email}', [o.email])
211
211
  self.verify(f'unlink account {o.email}')
212
212
  self.verify(f'list accounts -f {o.email}')
213
+
214
+ def test_webpage_source_cli(self):
215
+ o = make_test_values(lambda: None)
216
+
217
+ self.verify(f'add webpage --url "{o.webpage_url}"')
218
+
219
+ file_key = f'"#file#test-nonexistent-{epoch_micro()}-2.txt"'
220
+
221
+ self.verify(f'link webpage-source "{o.webpage_key}" {file_key}',
222
+ expected_stderr=['not found'])
223
+ self.verify(f'unlink webpage-source "{o.webpage_key}" {file_key}',
224
+ expected_stdout=[o.webpage_key])
225
+
226
+ # Clean up
227
+ self.verify(f'delete webpage "{o.webpage_key}"', ignore_stdout=True)
213
228
 
214
229
  def test_integration_cli(self):
215
230
  self.verify('list integrations', ignore_stdout=True)
@@ -258,6 +273,20 @@ class TestZCli:
258
273
 
259
274
  self.verify(f'delete configuration "{o.configuration_key}"', ignore_stdout=True)
260
275
 
276
+ def test_webapplication_cli(self):
277
+ o = make_test_values(lambda: None)
278
+ self.verify(f'add asset --dns "{o.webapp_name}" --name "{o.webapp_url}" --type webapplication')
279
+ self.verify(f'get asset "{o.webapp_key}"', expected_stdout=[o.webapp_key, o.webapp_url, o.webapp_name, '"status"', '"A"'])
280
+ self.verify(f'list assets -f "{o.webapp_name}"', expected_stdout=[o.webapp_key])
281
+ self.verify(f'delete asset "{o.webapp_key}"', ignore_stdout=True)
282
+
283
+ def test_webpage_cli(self):
284
+ o = make_test_values(lambda: None)
285
+ self.verify(f'add webpage --url "{o.webpage_url}"')
286
+ self.verify(f'get webpage "{o.webpage_key}"', expected_stdout=[o.webpage_key, o.webpage_url, '"status"', '"A"'])
287
+ self.verify(f'list webpages -p all -f "{o.webpage_url[:len(o.webpage_url)//2]}"', expected_stdout=[o.webpage_key])
288
+ self.verify(f'delete webpage "{o.webpage_key}"', ignore_stdout=True)
289
+
261
290
  def test_help_cli(self):
262
291
  self.verify('--help', ignore_stdout=True)
263
292
  self.verify('list --help', ignore_stdout=True)
@@ -310,9 +339,11 @@ class TestZCli:
310
339
 
311
340
  self.verify('link --help', ignore_stdout=True)
312
341
  self.verify('link account --help', ignore_stdout=True)
342
+ self.verify('link webpage-source --help', ignore_stdout=True)
313
343
 
314
344
  self.verify('unlink --help', ignore_stdout=True)
315
345
  self.verify('unlink account --help', ignore_stdout=True)
346
+ self.verify('unlink webpage-source --help', ignore_stdout=True)
316
347
 
317
348
  self.verify('delete --help', ignore_stdout=True)
318
349
  self.verify('delete asset --help', ignore_stdout=True)
@@ -66,6 +66,11 @@ def make_test_values(o):
66
66
  o.configuration_value = {o.configuration_name: o.configuration_name}
67
67
  o.configuration_key = configuration_key(o.configuration_name)
68
68
  o.key_name = f'test-key-name-{epoch_micro()}'
69
+ o.webapp_name = f'test-webapp-name-{epoch_micro()}'
70
+ o.webapp_url = f'https://test-webapp-{epoch_micro()}.com/'
71
+ o.webapp_key = f'#webapplication#{o.webapp_url}'
72
+ o.webpage_url = f'https://test-webpage-{epoch_micro()}.com/index.html'
73
+ o.webpage_key = f'#webpage#{o.webpage_url}'
69
74
  return o
70
75
 
71
76
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: praetorian-cli
3
- Version: 2.2.3
3
+ Version: 2.2.4
4
4
  Summary: For interacting with the Chariot API
5
5
  Home-page: https://github.com/praetorian-inc/praetorian-cli
6
6
  Author: Praetorian
@@ -62,6 +62,7 @@ praetorian_cli/sdk/entities/seeds.py
62
62
  praetorian_cli/sdk/entities/settings.py
63
63
  praetorian_cli/sdk/entities/statistics.py
64
64
  praetorian_cli/sdk/entities/webhook.py
65
+ praetorian_cli/sdk/entities/webpage.py
65
66
  praetorian_cli/sdk/model/__init__.py
66
67
  praetorian_cli/sdk/model/aegis.py
67
68
  praetorian_cli/sdk/model/globals.py
@@ -87,6 +88,7 @@ praetorian_cli/sdk/test/test_search.py
87
88
  praetorian_cli/sdk/test/test_seed.py
88
89
  praetorian_cli/sdk/test/test_setting.py
89
90
  praetorian_cli/sdk/test/test_webhook.py
91
+ praetorian_cli/sdk/test/test_webpage.py
90
92
  praetorian_cli/sdk/test/test_z_cli.py
91
93
  praetorian_cli/sdk/test/ui_mocks.py
92
94
  praetorian_cli/sdk/test/utils.py
@@ -1,6 +1,6 @@
1
1
  [metadata]
2
2
  name = praetorian-cli
3
- version = 2.2.3
3
+ version = 2.2.4
4
4
  author = Praetorian
5
5
  author_email = support@praetorian.com
6
6
  description = For interacting with the Chariot API
@@ -1,32 +0,0 @@
1
- import click
2
-
3
- from praetorian_cli.handlers.chariot import chariot
4
- from praetorian_cli.handlers.cli_decorators import cli_handler
5
-
6
-
7
- @chariot.group()
8
- def link():
9
- """ Add a collaborator to your account """
10
- pass
11
-
12
-
13
- @link.command()
14
- @cli_handler
15
- @click.argument('username')
16
- def account(chariot, username):
17
- """ Add a collaborator account to your account
18
-
19
- This allows them to assume access into your account
20
- and perform actions on your behalf.
21
-
22
- \b
23
- Arguments:
24
- - NAME: their email address
25
-
26
-
27
-
28
- \b
29
- Example usages:
30
- - praetorian chariot link account john@praetorian.com
31
- """
32
- chariot.accounts.add_collaborator(username)
@@ -1,27 +0,0 @@
1
- import click
2
-
3
- from praetorian_cli.handlers.chariot import chariot
4
- from praetorian_cli.handlers.cli_decorators import cli_handler
5
-
6
-
7
- @chariot.group()
8
- def unlink():
9
- """ Remove a collaborator from your account """
10
- pass
11
-
12
-
13
- @unlink.command()
14
- @cli_handler
15
- @click.argument('username')
16
- def account(chariot, username):
17
- """ Remove a collaborator account from your account. This will
18
- revoke their access to your account.
19
-
20
- Arguments:
21
- - NAME: Their email address.
22
-
23
- \b
24
- Example usages:
25
- - praetorian chariot unlink account john@praetorian.com
26
- """
27
- chariot.accounts.delete_collaborator(username)
File without changes
File without changes