synapse 2.202.0__py311-none-any.whl → 2.203.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 (100) hide show
  1. synapse/axon.py +4 -4
  2. synapse/cmds/cortex.py +4 -6
  3. synapse/cmds/hive.py +10 -10
  4. synapse/common.py +17 -58
  5. synapse/cortex.py +6 -6
  6. synapse/data/__init__.py +3 -2
  7. synapse/data/iana.uris.mpk +1 -0
  8. synapse/lib/autodoc.py +3 -3
  9. synapse/lib/cli.py +2 -2
  10. synapse/lib/config.py +2 -2
  11. synapse/lib/encoding.py +4 -3
  12. synapse/lib/httpapi.py +7 -11
  13. synapse/lib/json.py +224 -0
  14. synapse/lib/lmdbslab.py +1 -1
  15. synapse/lib/oauth.py +176 -54
  16. synapse/lib/rstorm.py +18 -14
  17. synapse/lib/schemas.py +87 -1
  18. synapse/lib/scrape.py +35 -13
  19. synapse/lib/snap.py +2 -1
  20. synapse/lib/storm.py +2 -2
  21. synapse/lib/stormhttp.py +11 -13
  22. synapse/lib/stormlib/aha.py +4 -4
  23. synapse/lib/stormlib/auth.py +1 -1
  24. synapse/lib/stormlib/cache.py +2 -2
  25. synapse/lib/stormlib/cortex.py +5 -5
  26. synapse/lib/stormlib/graph.py +1 -1
  27. synapse/lib/stormlib/imap.py +1 -1
  28. synapse/lib/stormlib/json.py +8 -11
  29. synapse/lib/stormlib/model.py +1 -1
  30. synapse/lib/stormlib/notifications.py +2 -2
  31. synapse/lib/stormlib/oauth.py +105 -2
  32. synapse/lib/stormlib/stats.py +4 -0
  33. synapse/lib/stormlib/stix.py +3 -4
  34. synapse/lib/stormlib/vault.py +6 -6
  35. synapse/lib/stormlib/xml.py +2 -2
  36. synapse/lib/stormtypes.py +19 -28
  37. synapse/lib/structlog.py +3 -3
  38. synapse/lib/types.py +2 -1
  39. synapse/lib/version.py +2 -2
  40. synapse/lib/view.py +7 -3
  41. synapse/models/base.py +51 -2
  42. synapse/telepath.py +5 -3
  43. synapse/tests/files/__init__.py +0 -1
  44. synapse/tests/test_axon.py +1 -1
  45. synapse/tests/test_cmds_cortex.py +3 -2
  46. synapse/tests/test_cmds_hive.py +4 -4
  47. synapse/tests/test_common.py +29 -19
  48. synapse/tests/test_cortex.py +5 -5
  49. synapse/tests/test_lib_ast.py +3 -3
  50. synapse/tests/test_lib_autodoc.py +5 -5
  51. synapse/tests/test_lib_base.py +1 -1
  52. synapse/tests/test_lib_cell.py +16 -10
  53. synapse/tests/test_lib_config.py +2 -2
  54. synapse/tests/test_lib_encoding.py +2 -2
  55. synapse/tests/test_lib_grammar.py +64 -64
  56. synapse/tests/test_lib_httpapi.py +13 -13
  57. synapse/tests/test_lib_json.py +219 -0
  58. synapse/tests/test_lib_multislabseqn.py +2 -1
  59. synapse/tests/test_lib_node.py +2 -2
  60. synapse/tests/test_lib_scrape.py +50 -0
  61. synapse/tests/test_lib_storm.py +6 -6
  62. synapse/tests/test_lib_stormhttp.py +4 -4
  63. synapse/tests/test_lib_stormlib_auth.py +3 -2
  64. synapse/tests/test_lib_stormlib_cortex.py +10 -12
  65. synapse/tests/test_lib_stormlib_infosec.py +2 -3
  66. synapse/tests/test_lib_stormlib_json.py +18 -21
  67. synapse/tests/test_lib_stormlib_log.py +1 -1
  68. synapse/tests/test_lib_stormlib_oauth.py +603 -1
  69. synapse/tests/test_lib_stormlib_stats.py +13 -3
  70. synapse/tests/test_lib_stormlib_stix.py +5 -5
  71. synapse/tests/test_lib_stormtypes.py +4 -4
  72. synapse/tests/test_lib_structlog.py +5 -6
  73. synapse/tests/test_lib_view.py +8 -0
  74. synapse/tests/test_model_base.py +32 -0
  75. synapse/tests/test_model_infotech.py +2 -2
  76. synapse/tests/test_telepath.py +0 -1
  77. synapse/tests/test_tools_cryo_cat.py +4 -3
  78. synapse/tests/test_tools_docker_validate.py +4 -2
  79. synapse/tests/test_tools_feed.py +30 -2
  80. synapse/tests/test_tools_genpkg.py +1 -1
  81. synapse/tests/test_tools_healthcheck.py +8 -7
  82. synapse/tests/test_utils.py +2 -2
  83. synapse/tests/utils.py +3 -3
  84. synapse/tools/autodoc.py +3 -3
  85. synapse/tools/changelog.py +2 -2
  86. synapse/tools/cryo/cat.py +3 -3
  87. synapse/tools/csvtool.py +2 -3
  88. synapse/tools/docker/validate.py +5 -5
  89. synapse/tools/feed.py +2 -1
  90. synapse/tools/genpkg.py +3 -2
  91. synapse/tools/healthcheck.py +2 -3
  92. synapse/tools/json2mpk.py +2 -2
  93. synapse/utils/getrefs.py +6 -6
  94. synapse/vendor/cpython/lib/json.py +35 -0
  95. synapse/vendor/cpython/lib/test/test_json.py +22 -0
  96. {synapse-2.202.0.dist-info → synapse-2.203.0.dist-info}/METADATA +2 -1
  97. {synapse-2.202.0.dist-info → synapse-2.203.0.dist-info}/RECORD +100 -95
  98. {synapse-2.202.0.dist-info → synapse-2.203.0.dist-info}/WHEEL +1 -1
  99. {synapse-2.202.0.dist-info → synapse-2.203.0.dist-info}/LICENSE +0 -0
  100. {synapse-2.202.0.dist-info → synapse-2.203.0.dist-info}/top_level.txt +0 -0
@@ -1,15 +1,108 @@
1
+ import os
1
2
  import yarl
3
+ import base64
2
4
  import asyncio
5
+ import logging
3
6
 
4
7
  import synapse.exc as s_exc
5
8
  import synapse.common as s_common
9
+ import synapse.lib.cell as s_cell
6
10
  import synapse.lib.coro as s_coro
11
+ import synapse.lib.oauth as s_oauth
7
12
  import synapse.lib.httpapi as s_httpapi
8
13
  import synapse.tests.utils as s_test
9
14
  import synapse.tools.backup as s_backup
10
15
 
16
+ logger = logging.getLogger(__name__)
17
+
18
+ ESECRET = 'secret'
19
+ ECLIENT = 'root'
20
+ EASSERTION = 'secretassertion'
21
+ EASSERTION_TYPE = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'
22
+
23
+ class HttpOAuth2Assertion(s_httpapi.Handler):
24
+ async def get(self):
25
+ self.set_header('Content-Type', 'application/json')
26
+ reqv = [_s.decode() for _s in self.request.query_arguments.get('getassertion')]
27
+ if reqv == ['valid']:
28
+ self.set_status(200)
29
+ self.write({'assertion': EASSERTION})
30
+ elif reqv == ['invalid']:
31
+ self.set_status(401)
32
+ self.write({'error': 'not allowed'})
33
+ else:
34
+ self.set_status(200)
35
+ self.write({'assertion': 'newp'})
36
+
11
37
  class HttpOAuth2Token(s_httpapi.Handler):
12
38
 
39
+ def checkAuth(self, body):
40
+ # Assert client_secret or client_assertion is valid
41
+ if 'client_assertion' in body:
42
+ client_id = body['client_id'][0]
43
+ assertion = body['client_assertion'][0]
44
+ assertion_type = body['client_assertion_type'][0]
45
+ if client_id != ECLIENT:
46
+ self.set_status(400)
47
+ self.write({
48
+ 'error': 'invalid_request',
49
+ 'error_description': f'invalid client_id {client_id}'
50
+ })
51
+ return False
52
+ if assertion != EASSERTION:
53
+ self.set_status(400)
54
+ self.write({
55
+ 'error': 'invalid_request',
56
+ 'error_description': f'invalid client_assertion {assertion}'
57
+ })
58
+ return False
59
+ if assertion_type != EASSERTION_TYPE:
60
+ self.set_status(400)
61
+ self.write({
62
+ 'error': 'invalid_request',
63
+ 'error_description': f'invalid client_assertion_type {assertion_type}'
64
+ })
65
+ return False
66
+ else:
67
+ auth = self.request.headers.get('Authorization')
68
+ if auth is None:
69
+ self.set_status(400)
70
+ self.write({
71
+ 'error': 'invalid_request',
72
+ 'error_description': 'missing AUTHORIZATION header :('
73
+ })
74
+ return False
75
+
76
+ if not auth.startswith('Basic '):
77
+ self.set_status(400)
78
+ self.write({
79
+ 'error': 'invalid_request',
80
+ 'error_description': f'basic auth missing Basic ?'
81
+ })
82
+ return False
83
+
84
+ _, blob = auth.split(None, 1)
85
+
86
+ try:
87
+ text = base64.b64decode(blob).decode('utf8')
88
+ name, secret = text.split(':', 1)
89
+ except Exception as e:
90
+ self.set_status(400)
91
+ self.write({
92
+ 'error': 'invalid_request',
93
+ 'error_description': f'failed to decode auth {text=} {e=}'
94
+ })
95
+ return False
96
+ if secret != ESECRET:
97
+ self.set_status(400)
98
+ self.write({
99
+ 'error': 'invalid_request',
100
+ 'error_description': f'bad client_secret {secret}'
101
+ })
102
+ return False
103
+
104
+ return True
105
+
13
106
  async def post(self):
14
107
 
15
108
  body = {k: [vv.decode() for vv in v] for k, v in self.request.body_arguments.items()}
@@ -20,6 +113,9 @@ class HttpOAuth2Token(s_httpapi.Handler):
20
113
 
21
114
  if grant_type == 'authorization_code':
22
115
 
116
+ if not self.checkAuth(body):
117
+ return
118
+
23
119
  if body.get('code_verifier') != ['legit']:
24
120
  self.set_status(400)
25
121
  return self.write({
@@ -91,6 +187,9 @@ class HttpOAuth2Token(s_httpapi.Handler):
91
187
 
92
188
  if grant_type == 'refresh_token':
93
189
 
190
+ if not self.checkAuth(body):
191
+ return
192
+
94
193
  tok = body['refresh_token'][0]
95
194
 
96
195
  if tok.startswith('refreshtoken'):
@@ -246,7 +345,7 @@ class OAuthTest(s_test.SynTest):
246
345
  with self.raises(s_exc.StormRuntimeError):
247
346
  await core.callStorm(q)
248
347
 
249
- async def test_storm_oauth_v2(self):
348
+ async def test_storm_oauth_v2_clientsecret(self):
250
349
 
251
350
  with self.getTestDir() as dirn:
252
351
 
@@ -641,3 +740,506 @@ class OAuthTest(s_test.SynTest):
641
740
  self.true(await s_coro.event_wait(core01._oauth_sched_empty, timeout=5))
642
741
  self.len(0, core01._oauth_sched_heap)
643
742
  self.eq({'error': 'User does not exist'}, await core01.getOAuthClient(expconf00['iden'], user.iden))
743
+
744
+ async def test_storm_oauth_v2_clientassertion_callstorm(self):
745
+
746
+ with self.getTestDir() as dirn:
747
+
748
+ core00dirn = s_common.gendir(dirn, 'core00')
749
+ core01dirn = s_common.gendir(dirn, 'core01')
750
+
751
+ coreconf = {
752
+ 'nexslog:en': True,
753
+ }
754
+
755
+ async with self.getTestCore(dirn=core00dirn, conf=coreconf) as core00:
756
+ pass
757
+
758
+ s_backup.backup(core00dirn, core01dirn)
759
+
760
+ async with self.getTestCore(dirn=core00dirn, conf=coreconf) as core00:
761
+
762
+ conf = {'mirror': core00.getLocalUrl()}
763
+ async with self.getTestCore(dirn=core01dirn, conf=conf) as core01:
764
+
765
+ root = await core00.auth.getUserByName('root')
766
+ await root.setPasswd('secret')
767
+
768
+ user = await core00.auth.addUser('user')
769
+ await user.setPasswd('secret')
770
+ await core00.addUserRule(user.iden, (True, ('globals', 'get')))
771
+
772
+ core00.addHttpApi('/api/oauth/token', HttpOAuth2Token, {'cell': core00})
773
+ core00.addHttpApi('/api/oauth/assertion', HttpOAuth2Assertion, {'cell': core00})
774
+
775
+ addr, port = await core00.addHttpsPort(0)
776
+ baseurl = f'https://127.0.0.1:{port}'
777
+
778
+ view = await core01.callStorm('return($lib.view.get().iden)')
779
+ await core01.callStorm('$lib.globals.set(getassertion, valid)')
780
+
781
+ assert_q = '''
782
+ $url = `{$baseurl}/api/oauth/assertion`
783
+ $valid = $lib.globals.get(getassertion)
784
+ $raise = $lib.globals.get(raise, (false))
785
+ if $raise {
786
+ $lib.raise(BadAssertion, 'I am supposed to raise.')
787
+ }
788
+ $params = ({"getassertion": $valid})
789
+ $resp = $lib.inet.http.get($url, params=$params, ssl_verify=(false))
790
+ if ($resp.code = 200) {
791
+ $resp = ( (true), ({'token': $resp.json().assertion}))
792
+ } else {
793
+ $resp = ( (false), ({"error": `Failed to get assertion from {$url}`}) )
794
+ }
795
+ return ( $resp )
796
+ '''
797
+ assert_vars = {
798
+ 'baseurl': baseurl,
799
+ }
800
+
801
+ providerconf00 = {
802
+ 'iden': s_common.guid('providerconf00'),
803
+ 'name': 'providerconf00',
804
+ 'client_id': 'root',
805
+ 'client_assertion': {
806
+ 'cortex:callstorm': {
807
+ 'query': assert_q,
808
+ 'vars': assert_vars,
809
+ 'view': view,
810
+ }
811
+ },
812
+ 'auth_scheme': 'client_assertion',
813
+ 'scope': 'allthethings',
814
+ 'auth_uri': baseurl + '/api/oauth/authorize',
815
+ 'token_uri': baseurl + '/api/oauth/token',
816
+ 'redirect_uri': 'https://opticnetloc/oauth2',
817
+ 'extensions': {'pkce': True},
818
+ 'extra_auth_params': {'include_granted_scopes': 'true'}
819
+ }
820
+
821
+ self.notin('client_secret', providerconf00)
822
+
823
+ expconf00 = {
824
+ # default values
825
+ 'ssl_verify': True,
826
+ **providerconf00,
827
+ # default values currently not configurable by the user
828
+ 'flow_type': 'authorization_code',
829
+ }
830
+
831
+ opts = {
832
+ 'vars': {
833
+ 'providerconf': providerconf00,
834
+ 'authcode': 'itsagoodone',
835
+ 'code_verifier': 'legit',
836
+ 'lowuser': user.iden,
837
+ },
838
+ }
839
+ lowopts = opts.copy()
840
+ lowopts['user'] = user.iden
841
+
842
+ # add a new provider
843
+ await core01.nodes('$lib.inet.http.oauth.v2.addProvider($providerconf)', opts=opts)
844
+
845
+ # list providers
846
+ ret = await core01.callStorm('''
847
+ $confs = ([])
848
+ for ($iden, $conf) in $lib.inet.http.oauth.v2.listProviders() {
849
+ $confs.append(($iden, $conf))
850
+ }
851
+ return($confs)
852
+ ''')
853
+ self.eq([(expconf00['iden'], expconf00)], ret)
854
+
855
+ # get the provider by iden
856
+ ret = await core01.callStorm('''
857
+ return($lib.inet.http.oauth.v2.getProvider($providerconf.iden))
858
+ ''', opts=opts)
859
+ self.eq(expconf00, ret)
860
+
861
+ providerconf00['ssl_verify'] = False
862
+ expconf00['ssl_verify'] = False
863
+ await core01.nodes('''
864
+ $lib.inet.http.oauth.v2.delProvider($providerconf.iden)
865
+ $lib.inet.http.oauth.v2.addProvider($providerconf)
866
+ ''', opts=opts)
867
+
868
+ # set the user auth code
869
+ core00._oauth_sched_ran.clear()
870
+ await core01.nodes('''
871
+ $iden = $providerconf.iden
872
+ $lib.inet.http.oauth.v2.setUserAuthCode($iden, $authcode, code_verifier=$code_verifier)
873
+ ''', opts=lowopts)
874
+
875
+ # the token is available immediately
876
+ ret = await core01.callStorm('''
877
+ return($lib.inet.http.oauth.v2.getUserAccessToken($providerconf.iden))
878
+ ''', opts=lowopts)
879
+ self.eq((True, 'accesstoken00'), ret)
880
+
881
+ # access token refreshes in the background and refresh_token also gets updated
882
+ self.true(await s_coro.event_wait(core00._oauth_sched_ran, timeout=15))
883
+ await core01.sync()
884
+ clientconf = await core01.getOAuthClient(providerconf00['iden'], user.iden)
885
+ self.eq('accesstoken01', clientconf['access_token'])
886
+ self.eq('refreshtoken01', clientconf['refresh_token'])
887
+ self.eq(core00._oauth_sched_heap[0][0], clientconf['refresh_at'])
888
+
889
+ # Refresh again but raise an exception from callStorm
890
+ await core00.callStorm('$lib.globals.set(raise, (true))')
891
+ core00._oauth_sched_ran.clear()
892
+ self.true(await s_coro.event_wait(core00._oauth_sched_ran, timeout=15))
893
+ await core01.sync()
894
+ clientconf = await core01.getOAuthClient(providerconf00['iden'], user.iden)
895
+ self.isin("Error executing callStorm: StormRaise: errname='BadAssertion'", clientconf.get('error'))
896
+ self.notin('access_token', clientconf)
897
+ self.notin('refresh_token', clientconf)
898
+ await core00.callStorm('$lib.globals.pop(raise)')
899
+ self.true(await s_coro.event_wait(core00._oauth_sched_empty, timeout=5))
900
+ self.len(0, core00._oauth_sched_heap)
901
+
902
+ # clear the access token so new auth code will be needed
903
+ core00._oauth_sched_empty.clear()
904
+ ret = await core01.callStorm('''
905
+ return($lib.inet.http.oauth.v2.clearUserAccessToken($providerconf.iden))
906
+ ''', opts=lowopts)
907
+ self.isin('error', ret)
908
+ self.notin('access_token', ret)
909
+ self.notin('refresh_token', ret)
910
+
911
+ await core01.sync()
912
+ ret = await core01.callStorm('''
913
+ return($lib.inet.http.oauth.v2.getUserAccessToken($providerconf.iden))
914
+ ''', opts=lowopts)
915
+ self.eq((False, 'Auth code has not been set'), ret)
916
+
917
+ # An invalid assertion when setting the token code will cause an error
918
+ await core01.callStorm('$lib.globals.set(getassertion, newpnewp)')
919
+ with self.raises(s_exc.SynErr) as cm:
920
+ await core01.nodes('''
921
+ $iden = $providerconf.iden
922
+ $lib.inet.http.oauth.v2.setUserAuthCode($iden, $authcode, code_verifier=$code_verifier)
923
+ ''', opts=lowopts)
924
+ self.isin('Failed to get OAuth v2 token: invalid_request', cm.exception.get('mesg'))
925
+
926
+ # An assertion storm callback which fails to return a token as expected also produces an error
927
+ await core01.callStorm('$lib.globals.set(getassertion, invalid)')
928
+
929
+ with self.raises(s_exc.SynErr) as cm:
930
+ await core01.nodes('''
931
+ $iden = $providerconf.iden
932
+ $lib.inet.http.oauth.v2.setUserAuthCode($iden, $authcode, code_verifier=$code_verifier)
933
+ ''', opts=lowopts)
934
+ # N.B. The message here comes from the caller defined Storm callback. Not the oauth.py code.
935
+ self.isin('Failed to get OAuth v2 token: Failed to get assertion from',
936
+ cm.exception.get('mesg'))
937
+
938
+ async def test_storm_oauth_v2_clientassertion_azure_token(self):
939
+ with self.getTestDir() as dirn:
940
+ core00dirn = s_common.gendir(dirn, 'core00')
941
+ core01dirn = s_common.gendir(dirn, 'core01')
942
+
943
+ coreconf = {
944
+ 'nexslog:en': True,
945
+ }
946
+
947
+ async with self.getTestCore(dirn=core00dirn, conf=coreconf) as core00:
948
+ pass
949
+
950
+ s_backup.backup(core00dirn, core01dirn)
951
+
952
+ async with self.getTestCore(dirn=core00dirn, conf=coreconf) as core00:
953
+ conf = {'mirror': core00.getLocalUrl()}
954
+ async with self.getTestCore(dirn=core01dirn, conf=conf) as core01:
955
+ root = await core00.auth.getUserByName('root')
956
+ await root.setPasswd('secret')
957
+
958
+ user = await core00.auth.addUser('user')
959
+ await user.setPasswd('secret')
960
+
961
+ core00.addHttpApi('/api/oauth/token', HttpOAuth2Token, {'cell': core00})
962
+ core00.addHttpApi('/api/oauth/assertion', HttpOAuth2Assertion, {'cell': core00})
963
+
964
+ addr, port = await core00.addHttpsPort(0)
965
+ baseurl = f'https://127.0.0.1:{port}'
966
+
967
+ isok, valu = s_oauth._getAzureTokenFile()
968
+ self.false(isok)
969
+ self.eq(valu, 'AZURE_FEDERATED_TOKEN_FILE environment variable is not set.')
970
+
971
+ isok, valu = s_oauth._getAzureClientId()
972
+ self.false(isok)
973
+ self.eq(valu, 'AZURE_CLIENT_ID environment variable is not set.')
974
+
975
+ tokenpath = s_common.genpath(dirn, 'tokenfile')
976
+
977
+ providerconf00 = {
978
+ 'iden': s_common.guid('providerconf00'),
979
+ 'name': 'providerconf00',
980
+ 'client_assertion': {
981
+ 'msft:azure:workloadidentity': {'token': True, 'client_id': True}
982
+ },
983
+ 'auth_scheme': 'client_assertion',
984
+ 'scope': 'allthethings',
985
+ 'auth_uri': baseurl + '/api/oauth/authorize',
986
+ 'token_uri': baseurl + '/api/oauth/token',
987
+ 'redirect_uri': 'https://opticnetloc/oauth2',
988
+ 'extensions': {'pkce': True},
989
+ 'extra_auth_params': {'include_granted_scopes': 'true'}
990
+ }
991
+ opts = {
992
+ 'vars': {
993
+ 'providerconf': providerconf00,
994
+ 'authcode': 'itsagoodone',
995
+ 'code_verifier': 'legit',
996
+ 'lowuser': user.iden,
997
+ },
998
+ }
999
+
1000
+ # must be able to get the token value to create the provider
1001
+ with self.raises(s_exc.BadArg) as cm:
1002
+ await core01.nodes('$lib.inet.http.oauth.v2.addProvider($providerconf)', opts=opts)
1003
+ self.isin('Failed to get the client_assertion data', cm.exception.get('mesg'))
1004
+
1005
+ with self.setTstEnvars(AZURE_CLIENT_ID=''):
1006
+ isok, valu = s_oauth._getAzureClientId()
1007
+ self.false(isok)
1008
+ self.eq(valu, 'AZURE_CLIENT_ID is set to an empty string.')
1009
+
1010
+ with self.setTstEnvars(AZURE_FEDERATED_TOKEN_FILE=tokenpath, AZURE_CLIENT_ID='root'):
1011
+ isok, valu = s_oauth._getAzureTokenFile()
1012
+ self.false(isok)
1013
+ self.eq(valu, f'AZURE_FEDERATED_TOKEN_FILE file does not exist {tokenpath}')
1014
+
1015
+ with s_common.genfile(tokenpath) as fd:
1016
+ fd.write(EASSERTION.encode())
1017
+
1018
+ isok, valu = s_oauth._getAzureTokenFile()
1019
+ self.true(isok)
1020
+ self.eq(valu, EASSERTION)
1021
+
1022
+ isok, valu = s_oauth._getAzureClientId()
1023
+ self.true(isok)
1024
+ self.eq(valu, 'root')
1025
+
1026
+ expconf00 = {
1027
+ # default values
1028
+ 'ssl_verify': True,
1029
+ **providerconf00,
1030
+ # default values currently not configurable by the user
1031
+ 'flow_type': 'authorization_code',
1032
+ }
1033
+
1034
+ lowopts = opts.copy()
1035
+ lowopts['user'] = user.iden
1036
+
1037
+ # add a new provider
1038
+ await core01.nodes('$lib.inet.http.oauth.v2.addProvider($providerconf)', opts=opts)
1039
+
1040
+ # list providers
1041
+ ret = await core01.callStorm('''
1042
+ $confs = ([])
1043
+ for ($iden, $conf) in $lib.inet.http.oauth.v2.listProviders() {
1044
+ $confs.append(($iden, $conf))
1045
+ }
1046
+ return($confs)
1047
+ ''')
1048
+ self.eq([(expconf00['iden'], expconf00)], ret)
1049
+
1050
+ # get the provider by iden
1051
+ ret = await core01.callStorm('return($lib.inet.http.oauth.v2.getProvider($providerconf.iden))',
1052
+ opts=opts)
1053
+ self.eq(expconf00, ret)
1054
+
1055
+ providerconf00['ssl_verify'] = False
1056
+ expconf00['ssl_verify'] = False
1057
+ await core01.nodes('''
1058
+ $lib.inet.http.oauth.v2.delProvider($providerconf.iden)
1059
+ $lib.inet.http.oauth.v2.addProvider($providerconf)
1060
+ ''', opts=opts)
1061
+
1062
+ # set the user auth code
1063
+ core00._oauth_sched_ran.clear()
1064
+ await core01.nodes('''
1065
+ $iden = $providerconf.iden
1066
+ $lib.inet.http.oauth.v2.setUserAuthCode($iden, $authcode, code_verifier=$code_verifier)
1067
+ ''', opts=lowopts)
1068
+
1069
+ # the token is available immediately
1070
+ ret = await core01.callStorm('''
1071
+ return($lib.inet.http.oauth.v2.getUserAccessToken($providerconf.iden))
1072
+ ''', opts=lowopts)
1073
+ self.eq((True, 'accesstoken00'), ret)
1074
+
1075
+ # access token refreshes in the background and refresh_token also gets updated
1076
+ self.true(await s_coro.event_wait(core00._oauth_sched_ran, timeout=15))
1077
+ await core01.sync()
1078
+ clientconf = await core01.getOAuthClient(providerconf00['iden'], user.iden)
1079
+ self.eq('accesstoken01', clientconf['access_token'])
1080
+ self.eq('refreshtoken01', clientconf['refresh_token'])
1081
+ self.eq(core00._oauth_sched_heap[0][0], clientconf['refresh_at'])
1082
+
1083
+ # clear the auth code, delete the file and set the auth code
1084
+ core00._oauth_sched_empty.clear()
1085
+ ret = await core01.callStorm('''
1086
+ return($lib.inet.http.oauth.v2.clearUserAccessToken($providerconf.iden))
1087
+ ''', opts=lowopts)
1088
+ self.eq('accesstoken01', ret['access_token'])
1089
+ self.eq('refreshtoken01', ret['refresh_token'])
1090
+
1091
+ await core01.sync()
1092
+ ret = await core01.callStorm('''
1093
+ return($lib.inet.http.oauth.v2.getUserAccessToken($providerconf.iden))
1094
+ ''', opts=lowopts)
1095
+ self.eq((False, 'Auth code has not been set'), ret)
1096
+
1097
+ self.true(await s_coro.event_wait(core00._oauth_sched_empty, timeout=5))
1098
+ self.len(0, core00._oauth_sched_heap)
1099
+
1100
+ os.unlink(tokenpath)
1101
+
1102
+ core00._oauth_sched_empty.clear()
1103
+ with self.raises(s_exc.SynErr) as cm:
1104
+ await core01.nodes('''
1105
+ $iden = $providerconf.iden
1106
+ $lib.inet.http.oauth.v2.setUserAuthCode($iden, $authcode, code_verifier=$code_verifier)
1107
+ ''', opts=lowopts)
1108
+ self.isin('Failed to get OAuth v2 token: AZURE_FEDERATED_TOKEN_FILE file does not exist',
1109
+ cm.exception.get('mesg'))
1110
+
1111
+ async def test_storm_oauth_v2_badconfigs(self):
1112
+ # Specifically test bad configs here
1113
+ async with self.getTestCore() as core:
1114
+ tokenfile = s_common.genpath(core.dirn, 'file.txt')
1115
+ with s_common.genfile(tokenfile) as fd:
1116
+ fd.write('token'.encode('utf-8'))
1117
+
1118
+ # Coverage for invalid configs ( we should never get into this state though! )
1119
+ # These checks would be triggered during future addition of new auth_schemes or
1120
+ # additional client_assertion providers.
1121
+ conf = {'auth_scheme': 'dne'}
1122
+ isok, info = await core._getAuthData(conf, '')
1123
+ self.false(isok)
1124
+ self.eq(info.get('error'), 'Unknown authorization scheme: dne')
1125
+
1126
+ conf = {'auth_scheme': 'client_assertion', 'client_id': '1234', 'client_assertion': {'key': 'dne'}}
1127
+ isok, info = await core._getAuthData(conf, '')
1128
+ self.false(isok)
1129
+ self.eq(info.get('error'), "Unknown client_assertions data: {'key': 'dne'}")
1130
+
1131
+ # Coverage for a weird configuration of azure workload identity
1132
+ with self.setTstEnvars(AZURE_FEDERATED_TOKEN_FILE=tokenfile):
1133
+ conf = {'auth_scheme': 'client_assertion',
1134
+ 'client_assertion': {'msft:azure:workloadidentity': {
1135
+ 'token': True,
1136
+ 'client_id': True,
1137
+ }}}
1138
+ isok, info = await core._getAuthData(conf, '')
1139
+ self.false(isok)
1140
+ self.eq(info.get('error'), "AZURE_CLIENT_ID environment variable is not set.")
1141
+
1142
+ view = await core.callStorm('return($lib.view.get().iden)')
1143
+
1144
+ providerconf00 = {
1145
+ 'iden': s_common.guid('providerconf00'),
1146
+ 'name': 'providerconf00',
1147
+ 'client_id': 'root',
1148
+ 'client_secret': 'secret',
1149
+ 'scope': 'allthethings',
1150
+ 'auth_uri': 'https://hehe.corp/api/oauth/authorize',
1151
+ 'token_uri': 'https://hehe.corp/api/oauth/token',
1152
+ 'redirect_uri': 'https://opticnetloc/oauth2',
1153
+ 'extensions': {'pkce': True},
1154
+ 'extra_auth_params': {'include_granted_scopes': 'true'}
1155
+ }
1156
+ opts = {
1157
+ 'vars': {
1158
+ 'providerconf': providerconf00,
1159
+ }
1160
+ }
1161
+ q = '$lib.inet.http.oauth.v2.addProvider($providerconf)'
1162
+
1163
+ providerconf00.pop('client_secret')
1164
+ with self.raises(s_exc.BadArg) as cm:
1165
+ await core.nodes(q, opts=opts)
1166
+ self.isin('client_assertion and client_secret missing', cm.exception.get('mesg'))
1167
+
1168
+ providerconf00['client_assertion'] = {'msft:azure:workloadidentity': {'token': True}}
1169
+ with self.raises(s_exc.BadArg) as cm:
1170
+ await core.nodes(q, opts=opts)
1171
+ self.isin('Must provide client_secret for auth_scheme=basic', cm.exception.get('mesg'))
1172
+
1173
+ providerconf00['client_secret'] = 'secret'
1174
+ providerconf00.pop('client_id')
1175
+ providerconf00.pop('client_assertion')
1176
+ with self.raises(s_exc.BadArg) as cm:
1177
+ await core.nodes(q, opts=opts)
1178
+ self.isin('Must provide client_id for auth_scheme=basic', cm.exception.get('mesg'))
1179
+ providerconf00['client_id'] = 'root'
1180
+
1181
+ providerconf00['auth_scheme'] = 'client_assertion'
1182
+
1183
+ callstormopts = {
1184
+ 'query': 'version',
1185
+ 'view': view,
1186
+ }
1187
+ assertions = {'msft:azure:workloadidentity': {'token': True}}
1188
+ providerconf00['client_secret'] = 'secret'
1189
+ providerconf00['client_assertion'] = assertions
1190
+ with self.raises(s_exc.BadArg) as cm:
1191
+ await core.nodes(q, opts=opts)
1192
+ self.isin('client_assertion and client_secret provided.', cm.exception.get('mesg'))
1193
+
1194
+ providerconf00.pop('client_secret')
1195
+ assertions['msft:azure:workloadidentity'] = {'token': False}
1196
+ with self.raises(s_exc.BadArg) as cm:
1197
+ await core.nodes(q, opts=opts)
1198
+ self.eq('msft:azure:workloadidentity token key must be true', cm.exception.get('mesg'))
1199
+
1200
+ with self.setTstEnvars(AZURE_FEDERATED_TOKEN_FILE=tokenfile):
1201
+ assertions['msft:azure:workloadidentity'] = {'token': True, 'client_id': True}
1202
+
1203
+ with self.raises(s_exc.BadArg) as cm:
1204
+ await core.nodes(q, opts=opts)
1205
+ m = 'Cannot specify a fixed client_id and a dynamic client_id value.'
1206
+ self.eq(m, cm.exception.get('mesg'))
1207
+
1208
+ providerconf00.pop('client_id')
1209
+ with self.raises(s_exc.BadArg) as cm:
1210
+ await core.nodes(q, opts=opts)
1211
+ m = 'Failed to get the client_id data: AZURE_CLIENT_ID environment variable is not set.'
1212
+ self.eq(m, cm.exception.get('mesg'))
1213
+
1214
+ providerconf00['client_id'] = 'root'
1215
+ assertions['cortex:callstorm'] = callstormopts
1216
+ with self.raises(s_exc.SchemaViolation) as cm:
1217
+ await core.nodes(q, opts=opts)
1218
+ self.isin('data.client_assertion must be valid exactly by one definition', cm.exception.get('mesg'))
1219
+
1220
+ assertions.pop('msft:azure:workloadidentity')
1221
+ callstormopts['view'] = s_common.guid()
1222
+ with self.raises(s_exc.BadArg) as cm:
1223
+ await core.nodes(q, opts=opts)
1224
+ self.eq(f'View {callstormopts["view"]} does not exist.', cm.exception.get('mesg'))
1225
+
1226
+ callstormopts['view'] = view
1227
+ callstormopts['query'] = ' | | | '
1228
+ with self.raises(s_exc.BadArg) as cm:
1229
+ await core.nodes(q, opts=opts)
1230
+ self.isin('Bad storm query', cm.exception.get('mesg'))
1231
+
1232
+ callstormopts['query'] = ' return ( ) '
1233
+ providerconf00.pop('client_id')
1234
+ with self.raises(s_exc.BadArg) as cm:
1235
+ await core.nodes(q, opts=opts)
1236
+ self.eq('Must provide client_id for with cortex:callstorm provider.', cm.exception.get('mesg'))
1237
+
1238
+ class NotACortex(s_oauth.OAuthMixin, s_cell.Cell):
1239
+ async def initServiceStorage(self):
1240
+ await self._initOAuthManager()
1241
+
1242
+ async with self.getTestCell(NotACortex) as cell:
1243
+ with self.raises(s_exc.BadArg) as cm:
1244
+ await cell.addOAuthProvider(providerconf00)
1245
+ self.eq('cortex:callstorm client assertion not supported by NotACortex', cm.exception.get('mesg'))