synapse 2.201.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.
- synapse/axon.py +4 -4
- synapse/cmds/cortex.py +4 -6
- synapse/cmds/hive.py +10 -10
- synapse/common.py +17 -58
- synapse/cortex.py +36 -29
- synapse/data/__init__.py +3 -2
- synapse/data/iana.uris.mpk +1 -0
- synapse/lib/autodoc.py +3 -3
- synapse/lib/base.py +2 -12
- synapse/lib/cell.py +9 -13
- synapse/lib/cli.py +2 -2
- synapse/lib/config.py +2 -2
- synapse/lib/encoding.py +4 -3
- synapse/lib/httpapi.py +7 -11
- synapse/lib/json.py +224 -0
- synapse/lib/lmdbslab.py +1 -1
- synapse/lib/oauth.py +176 -54
- synapse/lib/parser.py +2 -1
- synapse/lib/rstorm.py +18 -14
- synapse/lib/schemas.py +87 -1
- synapse/lib/scrape.py +35 -13
- synapse/lib/snap.py +2 -1
- synapse/lib/storm.lark +5 -4
- synapse/lib/storm.py +2 -2
- synapse/lib/storm_format.py +2 -1
- synapse/lib/stormhttp.py +11 -13
- synapse/lib/stormlib/aha.py +4 -4
- synapse/lib/stormlib/auth.py +1 -1
- synapse/lib/stormlib/cache.py +2 -2
- synapse/lib/stormlib/cortex.py +5 -5
- synapse/lib/stormlib/graph.py +1 -1
- synapse/lib/stormlib/imap.py +1 -1
- synapse/lib/stormlib/json.py +8 -11
- synapse/lib/stormlib/model.py +1 -1
- synapse/lib/stormlib/notifications.py +2 -2
- synapse/lib/stormlib/oauth.py +105 -2
- synapse/lib/stormlib/stats.py +4 -0
- synapse/lib/stormlib/stix.py +3 -4
- synapse/lib/stormlib/vault.py +6 -6
- synapse/lib/stormlib/xml.py +2 -2
- synapse/lib/stormtypes.py +19 -28
- synapse/lib/structlog.py +3 -3
- synapse/lib/types.py +2 -1
- synapse/lib/version.py +2 -2
- synapse/lib/view.py +7 -3
- synapse/models/base.py +51 -2
- synapse/telepath.py +79 -18
- synapse/tests/files/__init__.py +0 -1
- synapse/tests/test_axon.py +1 -1
- synapse/tests/test_cmds_cortex.py +3 -2
- synapse/tests/test_cmds_hive.py +4 -4
- synapse/tests/test_common.py +29 -19
- synapse/tests/test_cortex.py +28 -8
- synapse/tests/test_lib_ast.py +3 -3
- synapse/tests/test_lib_autodoc.py +5 -5
- synapse/tests/test_lib_base.py +1 -1
- synapse/tests/test_lib_cell.py +24 -7
- synapse/tests/test_lib_config.py +2 -2
- synapse/tests/test_lib_encoding.py +2 -2
- synapse/tests/test_lib_grammar.py +68 -64
- synapse/tests/test_lib_httpapi.py +13 -13
- synapse/tests/test_lib_json.py +219 -0
- synapse/tests/test_lib_multislabseqn.py +2 -1
- synapse/tests/test_lib_node.py +2 -2
- synapse/tests/test_lib_scrape.py +50 -0
- synapse/tests/test_lib_storm.py +12 -6
- synapse/tests/test_lib_stormhttp.py +4 -4
- synapse/tests/test_lib_stormlib_auth.py +3 -2
- synapse/tests/test_lib_stormlib_cortex.py +10 -12
- synapse/tests/test_lib_stormlib_infosec.py +2 -3
- synapse/tests/test_lib_stormlib_json.py +18 -21
- synapse/tests/test_lib_stormlib_log.py +1 -1
- synapse/tests/test_lib_stormlib_oauth.py +603 -1
- synapse/tests/test_lib_stormlib_stats.py +13 -3
- synapse/tests/test_lib_stormlib_stix.py +5 -5
- synapse/tests/test_lib_stormtypes.py +4 -4
- synapse/tests/test_lib_structlog.py +5 -6
- synapse/tests/test_lib_view.py +8 -0
- synapse/tests/test_model_base.py +32 -0
- synapse/tests/test_model_infotech.py +2 -2
- synapse/tests/test_telepath.py +56 -35
- synapse/tests/test_tools_cryo_cat.py +4 -3
- synapse/tests/test_tools_docker_validate.py +4 -2
- synapse/tests/test_tools_feed.py +30 -2
- synapse/tests/test_tools_genpkg.py +1 -1
- synapse/tests/test_tools_healthcheck.py +8 -7
- synapse/tests/test_utils.py +2 -2
- synapse/tests/test_utils_getrefs.py +35 -28
- synapse/tests/utils.py +3 -3
- synapse/tools/autodoc.py +3 -3
- synapse/tools/changelog.py +2 -2
- synapse/tools/cryo/cat.py +3 -3
- synapse/tools/csvtool.py +2 -3
- synapse/tools/docker/validate.py +5 -5
- synapse/tools/feed.py +2 -1
- synapse/tools/genpkg.py +3 -2
- synapse/tools/healthcheck.py +2 -3
- synapse/tools/json2mpk.py +2 -2
- synapse/utils/getrefs.py +10 -8
- synapse/vendor/cpython/lib/json.py +35 -0
- synapse/vendor/cpython/lib/test/test_json.py +22 -0
- {synapse-2.201.0.dist-info → synapse-2.203.0.dist-info}/METADATA +2 -1
- {synapse-2.201.0.dist-info → synapse-2.203.0.dist-info}/RECORD +106 -101
- {synapse-2.201.0.dist-info → synapse-2.203.0.dist-info}/WHEEL +1 -1
- {synapse-2.201.0.dist-info → synapse-2.203.0.dist-info}/LICENSE +0 -0
- {synapse-2.201.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
|
|
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'))
|