synapse 2.191.0__py311-none-any.whl → 2.193.0__py311-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of synapse might be problematic. Click here for more details.

Files changed (46) hide show
  1. synapse/axon.py +54 -23
  2. synapse/common.py +15 -0
  3. synapse/cortex.py +18 -20
  4. synapse/exc.py +6 -1
  5. synapse/lib/agenda.py +0 -2
  6. synapse/lib/ast.py +30 -12
  7. synapse/lib/cell.py +79 -85
  8. synapse/lib/cli.py +20 -11
  9. synapse/lib/nexus.py +2 -1
  10. synapse/lib/parser.py +1 -1
  11. synapse/lib/snap.py +4 -4
  12. synapse/lib/storm.py +34 -17
  13. synapse/lib/stormhttp.py +32 -35
  14. synapse/lib/stormlib/json.py +5 -2
  15. synapse/lib/stormtypes.py +121 -20
  16. synapse/lib/version.py +2 -2
  17. synapse/models/inet.py +17 -1
  18. synapse/models/infotech.py +14 -4
  19. synapse/models/risk.py +16 -2
  20. synapse/tests/test_axon.py +10 -0
  21. synapse/tests/test_cortex.py +55 -3
  22. synapse/tests/test_exc.py +3 -0
  23. synapse/tests/test_lib_agenda.py +157 -1
  24. synapse/tests/test_lib_ast.py +49 -1
  25. synapse/tests/test_lib_cell.py +106 -1
  26. synapse/tests/test_lib_httpapi.py +9 -2
  27. synapse/tests/test_lib_storm.py +72 -30
  28. synapse/tests/test_lib_stormhttp.py +57 -12
  29. synapse/tests/test_lib_stormlib_json.py +20 -0
  30. synapse/tests/test_lib_stormlib_scrape.py +2 -2
  31. synapse/tests/test_model_inet.py +40 -5
  32. synapse/tests/test_model_risk.py +2 -0
  33. synapse/tests/test_servers_univ.py +0 -12
  34. synapse/tests/test_tools_apikey.py +227 -0
  35. synapse/tests/test_tools_storm.py +95 -0
  36. synapse/tests/test_utils_getrefs.py +1 -1
  37. synapse/tools/apikey.py +93 -0
  38. synapse/utils/getrefs.py +14 -3
  39. synapse/vendor/cpython/lib/http/__init__.py +0 -0
  40. synapse/vendor/cpython/lib/http/cookies.py +59 -0
  41. synapse/vendor/cpython/lib/test/test_http_cookies.py +49 -0
  42. {synapse-2.191.0.dist-info → synapse-2.193.0.dist-info}/METADATA +2 -2
  43. {synapse-2.191.0.dist-info → synapse-2.193.0.dist-info}/RECORD +46 -41
  44. {synapse-2.191.0.dist-info → synapse-2.193.0.dist-info}/WHEEL +1 -1
  45. {synapse-2.191.0.dist-info → synapse-2.193.0.dist-info}/LICENSE +0 -0
  46. {synapse-2.191.0.dist-info → synapse-2.193.0.dist-info}/top_level.txt +0 -0
synapse/lib/cell.py CHANGED
@@ -459,6 +459,43 @@ class CellApi(s_base.Base):
459
459
  async def delRole(self, iden):
460
460
  return await self.cell.delRole(iden)
461
461
 
462
+ async def addUserApiKey(self, name, duration=None, useriden=None):
463
+ if useriden is None:
464
+ useriden = self.user.iden
465
+
466
+ if self.user.iden == useriden:
467
+ self.user.confirm(('auth', 'self', 'set', 'apikey'), default=True)
468
+ else:
469
+ self.user.confirm(('auth', 'user', 'set', 'apikey'))
470
+
471
+ return await self.cell.addUserApiKey(useriden, name, duration=duration)
472
+
473
+ async def listUserApiKeys(self, useriden=None):
474
+ if useriden is None:
475
+ useriden = self.user.iden
476
+
477
+ if self.user.iden == useriden:
478
+ self.user.confirm(('auth', 'self', 'set', 'apikey'), default=True)
479
+ else:
480
+ self.user.confirm(('auth', 'user', 'set', 'apikey'))
481
+
482
+ return await self.cell.listUserApiKeys(useriden)
483
+
484
+ async def delUserApiKey(self, iden):
485
+ apikey = await self.cell.getUserApiKey(iden)
486
+ if apikey is None:
487
+ mesg = f'User API key with {iden=} does not exist.'
488
+ raise s_exc.NoSuchIden(mesg=mesg, iden=iden)
489
+
490
+ useriden = apikey.get('user')
491
+
492
+ if self.user.iden == useriden:
493
+ self.user.confirm(('auth', 'self', 'set', 'apikey'), default=True)
494
+ else:
495
+ self.user.confirm(('auth', 'user', 'set', 'apikey'))
496
+
497
+ return await self.cell.delUserApiKey(iden)
498
+
462
499
  @adminapi()
463
500
  async def dyncall(self, iden, todo, gatekeys=()):
464
501
  return await self.cell.dyncall(iden, todo, gatekeys=gatekeys)
@@ -1095,6 +1132,8 @@ class Cell(s_nexus.Pusher, s_telepath.Aware):
1095
1132
  }
1096
1133
  SYSCTL_CHECK_FREQ = 60.0
1097
1134
 
1135
+ LOGGED_HTTPAPI_HEADERS = ('User-Agent',)
1136
+
1098
1137
  async def __anit__(self, dirn, conf=None, readonly=False, parent=None):
1099
1138
 
1100
1139
  # phase 1
@@ -2584,18 +2623,12 @@ class Cell(s_nexus.Pusher, s_telepath.Aware):
2584
2623
  walkpath(self.backdirn)
2585
2624
  return backups
2586
2625
 
2587
- async def iterBackupArchive(self, name, user):
2588
-
2589
- success = False
2590
- loglevel = logging.WARNING
2591
-
2592
- path = self._reqBackDirn(name)
2593
- cellguid = os.path.join(path, 'cell.guid')
2594
- if not os.path.isfile(cellguid):
2595
- mesg = 'Specified backup path has no cell.guid file.'
2596
- raise s_exc.BadArg(mesg=mesg, arg='path', valu=path)
2597
-
2626
+ async def _streamBackupArchive(self, path, user, name):
2598
2627
  link = s_scope.get('link')
2628
+ if link is None:
2629
+ mesg = 'Link not found in scope. This API must be called via a CellApi.'
2630
+ raise s_exc.SynErr(mesg=mesg)
2631
+
2599
2632
  linkinfo = await link.getSpawnInfo()
2600
2633
  linkinfo['logconf'] = await self._getSpawnLogConf()
2601
2634
 
@@ -2603,42 +2636,42 @@ class Cell(s_nexus.Pusher, s_telepath.Aware):
2603
2636
 
2604
2637
  ctx = multiprocessing.get_context('spawn')
2605
2638
 
2606
- proc = None
2607
- mesg = 'Streaming complete'
2608
-
2609
2639
  def getproc():
2610
2640
  proc = ctx.Process(target=_iterBackupProc, args=(path, linkinfo))
2611
2641
  proc.start()
2612
2642
  return proc
2613
2643
 
2644
+ mesg = 'Streaming complete'
2645
+ proc = await s_coro.executor(getproc)
2646
+ cancelled = False
2614
2647
  try:
2615
- proc = await s_coro.executor(getproc)
2616
-
2617
2648
  await s_coro.executor(proc.join)
2649
+ self.backlastuploaddt = datetime.datetime.now()
2650
+ logger.debug(f'Backup streaming completed successfully for {name}')
2618
2651
 
2619
- except (asyncio.CancelledError, Exception) as e:
2652
+ except asyncio.CancelledError:
2653
+ logger.warning('Backup streaming was cancelled.')
2654
+ cancelled = True
2655
+ raise
2620
2656
 
2621
- # We want to log all exceptions here, an asyncio.CancelledError
2622
- # could be the result of a remote link terminating due to the
2623
- # backup stream being completed, prior to this function
2624
- # finishing.
2657
+ except Exception as e:
2625
2658
  logger.exception('Error during backup streaming.')
2626
-
2627
- if proc:
2628
- proc.terminate()
2629
-
2630
2659
  mesg = repr(e)
2631
2660
  raise
2632
2661
 
2633
- else:
2634
- success = True
2635
- loglevel = logging.DEBUG
2636
- self.backlastuploaddt = datetime.datetime.now()
2637
-
2638
2662
  finally:
2639
- phrase = 'successfully' if success else 'with failure'
2640
- logger.log(loglevel, f'iterBackupArchive completed {phrase} for {name}')
2641
- raise s_exc.DmonSpawn(mesg=mesg)
2663
+ proc.terminate()
2664
+
2665
+ if not cancelled:
2666
+ raise s_exc.DmonSpawn(mesg=mesg)
2667
+
2668
+ async def iterBackupArchive(self, name, user):
2669
+ path = self._reqBackDirn(name)
2670
+ cellguid = os.path.join(path, 'cell.guid')
2671
+ if not os.path.isfile(cellguid):
2672
+ mesg = 'Specified backup path has no cell.guid file.'
2673
+ raise s_exc.BadArg(mesg=mesg, arg='path', valu=path)
2674
+ await self._streamBackupArchive(path, user, name)
2642
2675
 
2643
2676
  async def iterNewBackupArchive(self, user, name=None, remove=False):
2644
2677
 
@@ -2649,9 +2682,6 @@ class Cell(s_nexus.Pusher, s_telepath.Aware):
2649
2682
  if remove:
2650
2683
  self.backupstreaming = True
2651
2684
 
2652
- success = False
2653
- loglevel = logging.WARNING
2654
-
2655
2685
  if name is None:
2656
2686
  name = time.strftime('%Y%m%d%H%M%S', datetime.datetime.now().timetuple())
2657
2687
 
@@ -2660,10 +2690,6 @@ class Cell(s_nexus.Pusher, s_telepath.Aware):
2660
2690
  mesg = 'Backup with name already exists'
2661
2691
  raise s_exc.BadArg(mesg=mesg)
2662
2692
 
2663
- link = s_scope.get('link')
2664
- linkinfo = await link.getSpawnInfo()
2665
- linkinfo['logconf'] = await self._getSpawnLogConf()
2666
-
2667
2693
  try:
2668
2694
  await self.runBackup(name)
2669
2695
  except Exception:
@@ -2673,54 +2699,13 @@ class Cell(s_nexus.Pusher, s_telepath.Aware):
2673
2699
  logger.debug(f'Removed {path}')
2674
2700
  raise
2675
2701
 
2676
- await self.boss.promote('backup:stream', user=user, info={'name': name})
2677
-
2678
- ctx = multiprocessing.get_context('spawn')
2679
-
2680
- proc = None
2681
- mesg = 'Streaming complete'
2682
-
2683
- def getproc():
2684
- proc = ctx.Process(target=_iterBackupProc, args=(path, linkinfo))
2685
- proc.start()
2686
- return proc
2687
-
2688
- try:
2689
- proc = await s_coro.executor(getproc)
2690
-
2691
- await s_coro.executor(proc.join)
2692
-
2693
- except (asyncio.CancelledError, Exception) as e:
2694
-
2695
- # We want to log all exceptions here, an asyncio.CancelledError
2696
- # could be the result of a remote link terminating due to the
2697
- # backup stream being completed, prior to this function
2698
- # finishing.
2699
- logger.exception('Error during backup streaming.')
2700
-
2701
- if proc:
2702
- proc.terminate()
2703
-
2704
- mesg = repr(e)
2705
- raise
2706
-
2707
- else:
2708
- success = True
2709
- loglevel = logging.DEBUG
2710
- self.backlastuploaddt = datetime.datetime.now()
2711
-
2712
- finally:
2713
- if remove:
2714
- logger.debug(f'Removing {path}')
2715
- await s_coro.executor(shutil.rmtree, path, ignore_errors=True)
2716
- logger.debug(f'Removed {path}')
2717
-
2718
- phrase = 'successfully' if success else 'with failure'
2719
- logger.log(loglevel, f'iterNewBackupArchive completed {phrase} for {name}')
2720
- raise s_exc.DmonSpawn(mesg=mesg)
2702
+ await self._streamBackupArchive(path, user, name)
2721
2703
 
2722
2704
  finally:
2723
2705
  if remove:
2706
+ logger.debug(f'Removing {path}')
2707
+ await s_coro.executor(shutil.rmtree, path, ignore_errors=True)
2708
+ logger.debug(f'Removed {path}')
2724
2709
  self.backupstreaming = False
2725
2710
 
2726
2711
  async def isUserAllowed(self, iden, perm, gateiden=None, default=False):
@@ -3248,6 +3233,15 @@ class Cell(s_nexus.Pusher, s_telepath.Aware):
3248
3233
  'remoteip': remote_ip,
3249
3234
  }
3250
3235
 
3236
+ headers = {}
3237
+
3238
+ for header in self.LOGGED_HTTPAPI_HEADERS:
3239
+ if (valu := handler.request.headers.get(header)) is not None:
3240
+ headers[header.lower()] = valu
3241
+
3242
+ if headers:
3243
+ enfo['headers'] = headers
3244
+
3251
3245
  extra = {'synapse': enfo}
3252
3246
 
3253
3247
  # It is possible that a Cell implementor may register handlers which
synapse/lib/cli.py CHANGED
@@ -281,18 +281,26 @@ class Cli(s_base.Base):
281
281
 
282
282
  await self.fini()
283
283
 
284
- async def addSignalHandlers(self):
284
+ async def addSignalHandlers(self): # pragma: no cover
285
285
  '''
286
286
  Register SIGINT signal handler with the ioloop to cancel the currently running cmdloop task.
287
+ Removes the handler when the cli is fini'd.
287
288
  '''
288
-
289
289
  def sigint():
290
- self.printf('<ctrl-c>')
291
290
  if self.cmdtask is not None:
292
291
  self.cmdtask.cancel()
293
292
 
294
293
  self.loop.add_signal_handler(signal.SIGINT, sigint)
295
294
 
295
+ def onfini():
296
+ # N.B. This is reaches into some loop / handle internals but
297
+ # prevents us from removing a handler that overwrote our own.
298
+ hndl = self.loop._signal_handlers.get(signal.SIGINT, None) # type: asyncio.Handle
299
+ if hndl is not None and hndl._callback is sigint:
300
+ self.loop.remove_signal_handler(signal.SIGINT)
301
+
302
+ self.onfini(onfini)
303
+
296
304
  def get(self, name, defval=None):
297
305
  return self.locs.get(name, defval)
298
306
 
@@ -324,8 +332,12 @@ class Cli(s_base.Base):
324
332
  if text is None:
325
333
  text = self.cmdprompt
326
334
 
327
- with patch_stdout():
328
- retn = await self.sess.prompt_async(text, vi_mode=self.vi_mode, enable_open_in_editor=True)
335
+ with patch_stdout(): # pragma: no cover
336
+ retn = await self.sess.prompt_async(text,
337
+ vi_mode=self.vi_mode,
338
+ enable_open_in_editor=True,
339
+ handle_sigint=False # We handle sigint in the loop
340
+ )
329
341
  return retn
330
342
 
331
343
  def printf(self, mesg, addnl=True, color=None):
@@ -390,7 +402,7 @@ class Cli(s_base.Base):
390
402
  self.cmdtask = self.schedCoro(coro)
391
403
  await self.cmdtask
392
404
 
393
- except KeyboardInterrupt:
405
+ except (KeyboardInterrupt, asyncio.CancelledError):
394
406
 
395
407
  if self.isfini:
396
408
  return
@@ -408,11 +420,8 @@ class Cli(s_base.Base):
408
420
  if self.cmdtask is not None:
409
421
  self.cmdtask.cancel()
410
422
  try:
411
- self.cmdtask.result()
412
- except asyncio.CancelledError:
413
- # Wait a beat to let any remaining nodes to print out before we print the prompt
414
- await asyncio.sleep(1)
415
- except Exception:
423
+ await asyncio.wait_for(self.cmdtask, timeout=0.1)
424
+ except (asyncio.CancelledError, asyncio.TimeoutError):
416
425
  pass
417
426
 
418
427
  async def runCmdLine(self, line):
synapse/lib/nexus.py CHANGED
@@ -505,6 +505,7 @@ class NexsRoot(s_base.Base):
505
505
  if features.get('dynmirror'):
506
506
  await proxy.readyToMirror()
507
507
 
508
+ synvers = cellinfo['synapse']['version']
508
509
  cellvers = cellinfo['cell']['version']
509
510
  if cellvers > self.cell.VERSION:
510
511
  logger.error('Leader is a higher version than we are. Mirrors must be updated first. Entering read-only mode.')
@@ -537,7 +538,7 @@ class NexsRoot(s_base.Base):
537
538
  offs = self.nexslog.index()
538
539
 
539
540
  opts = {}
540
- if cellvers >= (2, 95, 0):
541
+ if synvers >= (2, 95, 0):
541
542
  opts['tellready'] = True
542
543
 
543
544
  genr = proxy.getNexusChanges(offs, **opts)
synapse/lib/parser.py CHANGED
@@ -507,7 +507,7 @@ class Parser:
507
507
  origexc = e.orig_exc
508
508
  if not isinstance(origexc, s_exc.SynErr):
509
509
  raise e.orig_exc # pragma: no cover
510
- origexc.errinfo['text'] = self.text
510
+ origexc.set('text', self.text)
511
511
  return s_exc.BadSyntax(**origexc.errinfo)
512
512
 
513
513
  elif isinstance(e, lark.exceptions.UnexpectedCharacters): # pragma: no cover
synapse/lib/snap.py CHANGED
@@ -363,9 +363,9 @@ class ProtoNode:
363
363
  valu, norminfo = prop.type.norm(valu)
364
364
  except s_exc.BadTypeValu as e:
365
365
  oldm = e.errinfo.get('mesg')
366
- e.errinfo['prop'] = prop.name
367
- e.errinfo['form'] = prop.form.name
368
- e.errinfo['mesg'] = f'Bad prop value {prop.full}={valu!r} : {oldm}'
366
+ e.update({'prop': prop.name,
367
+ 'form': prop.form.name,
368
+ 'mesg': f'Bad prop value {prop.full}={valu!r} : {oldm}'})
369
369
  if self.ctx.snap.strict:
370
370
  raise e
371
371
  await self.ctx.snap.warn(e)
@@ -493,7 +493,7 @@ class SnapEditor:
493
493
  try:
494
494
  valu, norminfo = form.type.norm(valu)
495
495
  except s_exc.BadTypeValu as e:
496
- e.errinfo['form'] = form.name
496
+ e.set('form', form.name)
497
497
  if self.snap.strict: raise e
498
498
  await self.snap.warn(f'addNode() BadTypeValu {form.name}={valu} {e}')
499
499
  return None
synapse/lib/storm.py CHANGED
@@ -5344,6 +5344,12 @@ class ParallelCmd(Cmd):
5344
5344
  inet:ipv4#foo | parallel { $place = $lib.import(foobar).lookup(:latlong) [ :place=$place ] }
5345
5345
 
5346
5346
  NOTE: Storm variables set within the parallel query pipelines do not interact.
5347
+
5348
+ NOTE: If there are inbound nodes to the parallel command, parallel pipelines will be created as each node
5349
+ is processed, up to the number specified by --size. If the number of nodes in the pipeline is less
5350
+ than the value specified by --size, additional pipelines with no inbound node will not be created.
5351
+ If there are no inbound nodes to the parallel command, the number of pipelines specified by --size
5352
+ will always be created.
5347
5353
  '''
5348
5354
  name = 'parallel'
5349
5355
  readonly = True
@@ -5400,19 +5406,33 @@ class ParallelCmd(Cmd):
5400
5406
  inq = asyncio.Queue(maxsize=size)
5401
5407
  outq = asyncio.Queue(maxsize=size)
5402
5408
 
5403
- async def pump():
5404
- try:
5405
- async for pumpitem in genr:
5406
- await inq.put(pumpitem)
5407
- [await inq.put(None) for i in range(size)]
5408
- except asyncio.CancelledError: # pragma: no cover
5409
- raise
5410
- except Exception as e:
5411
- await outq.put(e)
5412
-
5413
- base.schedCoro(pump())
5414
- for i in range(size):
5415
- base.schedCoro(self.pipeline(runt, query, inq, outq))
5409
+ tsks = 0
5410
+ try:
5411
+ while tsks < size:
5412
+ await inq.put(await genr.__anext__())
5413
+ base.schedCoro(self.pipeline(runt, query, inq, outq))
5414
+ tsks += 1
5415
+ except StopAsyncIteration:
5416
+ [await inq.put(None) for i in range(tsks)]
5417
+
5418
+ # If a full set of tasks were created, keep pumping nodes into the queue
5419
+ if tsks == size:
5420
+ async def pump():
5421
+ try:
5422
+ async for pumpitem in genr:
5423
+ await inq.put(pumpitem)
5424
+ [await inq.put(None) for i in range(size)]
5425
+ except Exception as e:
5426
+ await outq.put(e)
5427
+
5428
+ base.schedCoro(pump())
5429
+
5430
+ # If no tasks were created, make a full set
5431
+ elif tsks == 0:
5432
+ tsks = size
5433
+ for i in range(size):
5434
+ base.schedCoro(self.pipeline(runt, query, inq, outq))
5435
+ [await inq.put(None) for i in range(tsks)]
5416
5436
 
5417
5437
  exited = 0
5418
5438
  while True:
@@ -5423,7 +5443,7 @@ class ParallelCmd(Cmd):
5423
5443
 
5424
5444
  if item is None:
5425
5445
  exited += 1
5426
- if exited == size:
5446
+ if exited == tsks:
5427
5447
  return
5428
5448
  continue
5429
5449
 
@@ -5566,9 +5586,6 @@ class TeeCmd(Cmd):
5566
5586
 
5567
5587
  await outq.put(None)
5568
5588
 
5569
- except asyncio.CancelledError: # pragma: no cover
5570
- raise
5571
-
5572
5589
  except Exception as e:
5573
5590
  await outq.put(e)
5574
5591
 
synapse/lib/stormhttp.py CHANGED
@@ -91,12 +91,19 @@ class LibHttp(s_stormtypes.Lib):
91
91
 
92
92
  For APIs that accept an ssl_opts argument, the dictionary may contain the following values::
93
93
 
94
- {
94
+ ({
95
95
  'verify': <bool> - Perform SSL/TLS verification. Is overridden by the ssl_verify argument.
96
96
  'client_cert': <str> - PEM encoded full chain certificate for use in mTLS.
97
97
  'client_key': <str> - PEM encoded key for use in mTLS. Alternatively, can be included in client_cert.
98
98
  'ca_cert': <str> - A PEM encoded full chain CA certificate for use when verifying the request.
99
- }
99
+ })
100
+
101
+ For APIs that accept a proxy argument, the following values are supported::
102
+
103
+ $lib.null: Deprecated - Use the proxy defined by the http:proxy configuration option if set.
104
+ $lib.true: Use the proxy defined by the http:proxy configuration option if set.
105
+ $lib.false: Do not use the proxy defined by the http:proxy configuration option if set.
106
+ <str>: A proxy URL string.
100
107
  '''
101
108
  _storm_locals = (
102
109
  {'name': 'get', 'desc': 'Get the contents of a given URL.',
@@ -113,8 +120,8 @@ class LibHttp(s_stormtypes.Lib):
113
120
  'default': 300},
114
121
  {'name': 'allow_redirects', 'type': 'bool', 'desc': 'If set to false, do not follow redirects.',
115
122
  'default': True},
116
- {'name': 'proxy', 'type': ['bool', 'null', 'str'],
117
- 'desc': 'Set to a proxy URL string or $lib.false to disable proxy use.', 'default': None},
123
+ {'name': 'proxy', 'type': ['bool', 'str'],
124
+ 'desc': 'Configure proxy usage. See $lib.inet.http help for additional details.', 'default': True},
118
125
  {'name': 'ssl_opts', 'type': 'dict',
119
126
  'desc': 'Optional SSL/TLS options. See $lib.inet.http help for additional details.',
120
127
  'default': None},
@@ -145,8 +152,8 @@ class LibHttp(s_stormtypes.Lib):
145
152
  'and the corresponding file will be uploaded as the value for '
146
153
  'the field.',
147
154
  'default': None},
148
- {'name': 'proxy', 'type': ['bool', 'null', 'str'],
149
- 'desc': 'Set to a proxy URL string or $lib.false to disable proxy use.', 'default': None},
155
+ {'name': 'proxy', 'type': ['bool', 'str'],
156
+ 'desc': 'Configure proxy usage. See $lib.inet.http help for additional details.', 'default': True},
150
157
  {'name': 'ssl_opts', 'type': 'dict',
151
158
  'desc': 'Optional SSL/TLS options. See $lib.inet.http help for additional details.',
152
159
  'default': None},
@@ -167,8 +174,8 @@ class LibHttp(s_stormtypes.Lib):
167
174
  'default': 300, },
168
175
  {'name': 'allow_redirects', 'type': 'bool', 'desc': 'If set to true, follow redirects.',
169
176
  'default': False},
170
- {'name': 'proxy', 'type': ['bool', 'null', 'str'],
171
- 'desc': 'Set to a proxy URL string or $lib.false to disable proxy use.', 'default': None},
177
+ {'name': 'proxy', 'type': ['bool', 'str'],
178
+ 'desc': 'Configure proxy usage. See $lib.inet.http help for additional details.', 'default': True},
172
179
  {'name': 'ssl_opts', 'type': 'dict',
173
180
  'desc': 'Optional SSL/TLS options. See $lib.inet.http help for additional details.',
174
181
  'default': None},
@@ -200,8 +207,8 @@ class LibHttp(s_stormtypes.Lib):
200
207
  'and the corresponding file will be uploaded as the value for '
201
208
  'the field.',
202
209
  'default': None},
203
- {'name': 'proxy', 'type': ['bool', 'null', 'str'],
204
- 'desc': 'Set to a proxy URL string or $lib.false to disable proxy use.', 'default': None},
210
+ {'name': 'proxy', 'type': ['bool', 'str'],
211
+ 'desc': 'Configure proxy usage. See $lib.inet.http help for additional details.', 'default': True},
205
212
  {'name': 'ssl_opts', 'type': 'dict',
206
213
  'desc': 'Optional SSL/TLS options. See $lib.inet.http help for additional details.',
207
214
  'default': None},
@@ -221,8 +228,8 @@ class LibHttp(s_stormtypes.Lib):
221
228
  'default': 300},
222
229
  {'name': 'params', 'type': 'dict', 'desc': 'Optional parameters which may be passed to the connection request.',
223
230
  'default': None},
224
- {'name': 'proxy', 'type': ['bool', 'null', 'str'],
225
- 'desc': 'Set to a proxy URL string or $lib.false to disable proxy use.', 'default': None},
231
+ {'name': 'proxy', 'type': ['bool', 'str'],
232
+ 'desc': 'Configure proxy usage. See $lib.inet.http help for additional details.', 'default': True},
226
233
  {'name': 'ssl_opts', 'type': 'dict',
227
234
  'desc': 'Optional SSL/TLS options. See $lib.inet.http help for additional details.',
228
235
  'default': None},
@@ -308,23 +315,23 @@ class LibHttp(s_stormtypes.Lib):
308
315
  return s_common.httpcodereason(code)
309
316
 
310
317
  async def _httpEasyHead(self, url, headers=None, ssl_verify=True, params=None, timeout=300,
311
- allow_redirects=False, proxy=None, ssl_opts=None):
318
+ allow_redirects=False, proxy=True, ssl_opts=None):
312
319
  return await self._httpRequest('HEAD', url, headers=headers, ssl_verify=ssl_verify, params=params,
313
320
  timeout=timeout, allow_redirects=allow_redirects, proxy=proxy, ssl_opts=ssl_opts)
314
321
 
315
322
  async def _httpEasyGet(self, url, headers=None, ssl_verify=True, params=None, timeout=300,
316
- allow_redirects=True, proxy=None, ssl_opts=None):
323
+ allow_redirects=True, proxy=True, ssl_opts=None):
317
324
  return await self._httpRequest('GET', url, headers=headers, ssl_verify=ssl_verify, params=params,
318
325
  timeout=timeout, allow_redirects=allow_redirects, proxy=proxy, ssl_opts=ssl_opts)
319
326
 
320
327
  async def _httpPost(self, url, headers=None, json=None, body=None, ssl_verify=True,
321
- params=None, timeout=300, allow_redirects=True, fields=None, proxy=None, ssl_opts=None):
328
+ params=None, timeout=300, allow_redirects=True, fields=None, proxy=True, ssl_opts=None):
322
329
  return await self._httpRequest('POST', url, headers=headers, json=json, body=body,
323
330
  ssl_verify=ssl_verify, params=params, timeout=timeout,
324
331
  allow_redirects=allow_redirects, fields=fields, proxy=proxy, ssl_opts=ssl_opts)
325
332
 
326
333
  async def inetHttpConnect(self, url, headers=None, ssl_verify=True, timeout=300,
327
- params=None, proxy=None, ssl_opts=None):
334
+ params=None, proxy=True, ssl_opts=None):
328
335
 
329
336
  url = await s_stormtypes.tostr(url)
330
337
  headers = await s_stormtypes.toprim(headers)
@@ -338,15 +345,9 @@ class LibHttp(s_stormtypes.Lib):
338
345
 
339
346
  sock = await WebSocket.anit()
340
347
 
341
- if proxy is not None:
342
- self.runt.confirm(('storm', 'lib', 'inet', 'http', 'proxy'))
343
-
344
- if proxy is None:
345
- proxy = await self.runt.snap.core.getConfOpt('http:proxy')
346
-
347
348
  connector = None
348
- if proxy:
349
- connector = aiohttp_socks.ProxyConnector.from_url(proxy)
349
+ if proxyurl := await s_stormtypes.resolveCoreProxyUrl(proxy):
350
+ connector = aiohttp_socks.ProxyConnector.from_url(proxyurl)
350
351
 
351
352
  timeout = aiohttp.ClientTimeout(total=timeout)
352
353
  kwargs = {'timeout': timeout}
@@ -387,7 +388,7 @@ class LibHttp(s_stormtypes.Lib):
387
388
 
388
389
  async def _httpRequest(self, meth, url, headers=None, json=None, body=None,
389
390
  ssl_verify=True, params=None, timeout=300, allow_redirects=True,
390
- fields=None, proxy=None, ssl_opts=None):
391
+ fields=None, proxy=True, ssl_opts=None):
391
392
  meth = await s_stormtypes.tostr(meth)
392
393
  url = await s_stormtypes.tostr(url)
393
394
  json = await s_stormtypes.toprim(json)
@@ -407,19 +408,18 @@ class LibHttp(s_stormtypes.Lib):
407
408
 
408
409
  headers = s_stormtypes.strifyHttpArg(headers)
409
410
 
410
- if proxy is not None:
411
- self.runt.confirm(('storm', 'lib', 'inet', 'http', 'proxy'))
412
-
413
411
  if fields:
414
412
  if any(['sha256' in field for field in fields]):
415
413
  self.runt.confirm(('storm', 'lib', 'axon', 'wput'))
416
414
 
417
415
  kwargs = {}
418
- axonvers = self.runt.snap.core.axoninfo['synapse']['version']
419
- if axonvers >= s_stormtypes.AXON_MINVERS_PROXY:
416
+
417
+ ok, proxy = await s_stormtypes.resolveAxonProxyArg(proxy)
418
+ if ok:
420
419
  kwargs['proxy'] = proxy
421
420
 
422
421
  if ssl_opts is not None:
422
+ axonvers = self.runt.snap.core.axoninfo['synapse']['version']
423
423
  mesg = f'The ssl_opts argument requires an Axon Synapse version {s_stormtypes.AXON_MINVERS_SSLOPTS}, ' \
424
424
  f'but the Axon is running {axonvers}'
425
425
  s_version.reqVersion(axonvers, s_stormtypes.AXON_MINVERS_SSLOPTS, mesg=mesg)
@@ -432,12 +432,9 @@ class LibHttp(s_stormtypes.Lib):
432
432
 
433
433
  kwargs['ssl'] = self.runt.snap.core.getCachedSslCtx(opts=ssl_opts, verify=ssl_verify)
434
434
 
435
- if proxy is None:
436
- proxy = await self.runt.snap.core.getConfOpt('http:proxy')
437
-
438
435
  connector = None
439
- if proxy:
440
- connector = aiohttp_socks.ProxyConnector.from_url(proxy)
436
+ if proxyurl := await s_stormtypes.resolveCoreProxyUrl(proxy):
437
+ connector = aiohttp_socks.ProxyConnector.from_url(proxyurl)
441
438
 
442
439
  timeout = aiohttp.ClientTimeout(total=timeout)
443
440
 
@@ -93,6 +93,7 @@ class JsonLib(s_stormtypes.Lib):
93
93
  'type': {'type': 'function', '_funcname': '_jsonSave',
94
94
  'args': (
95
95
  {'name': 'item', 'type': 'any', 'desc': 'The item to be serialized as a JSON string.', },
96
+ {'name': 'indent', 'type': 'int', 'desc': 'Specify a number of spaces to indent with.', 'default': None},
96
97
  ),
97
98
  'returns': {'type': 'str', 'desc': 'The JSON serialized object.', }}},
98
99
  {'name': 'schema', 'desc': 'Get a JS schema validation object.',
@@ -115,10 +116,12 @@ class JsonLib(s_stormtypes.Lib):
115
116
  }
116
117
 
117
118
  @s_stormtypes.stormfunc(readonly=True)
118
- async def _jsonSave(self, item):
119
+ async def _jsonSave(self, item, indent=None):
120
+ indent = await s_stormtypes.toint(indent, noneok=True)
121
+
119
122
  try:
120
123
  item = await s_stormtypes.toprim(item)
121
- return json.dumps(item)
124
+ return json.dumps(item, indent=indent)
122
125
  except Exception as e:
123
126
  mesg = f'Argument is not JSON compatible: {item}'
124
127
  raise s_exc.MustBeJsonSafe(mesg=mesg)