1
1
# Copyright (c) Microsoft Corporation.
2
2
# Licensed under the MIT License.
3
3
import os
4
+ import json
4
5
import threading
5
6
import pytest
7
+ import jwt
8
+ import jwcrypto
9
+ from flask import Flask , jsonify
6
10
from werkzeug .serving import make_server
7
11
from scitt_emulator import cli , server
12
+ from scitt_emulator .oidc import OIDCAuthMiddleware
8
13
9
14
issuer = "did:web:example.com"
10
15
content_type = "application/json"
@@ -16,16 +21,23 @@ def execute_cli(argv):
16
21
17
22
18
23
class Service :
19
- def __init__ (self , config ):
24
+ def __init__ (self , config , create_flask_app = None ):
20
25
self .config = config
26
+ self .create_flask_app = (
27
+ create_flask_app
28
+ if create_flask_app is not None
29
+ else server .create_flask_app
30
+ )
21
31
22
32
def __enter__ (self ):
23
- app = server .create_flask_app (self .config )
24
- self .service_parameters_path = app .service_parameters_path
25
- host = "127.0.0.1"
26
- self .server = make_server (host , 0 , app )
33
+ app = self .create_flask_app (self .config )
34
+ if hasattr (app , "service_parameters_path" ):
35
+ self .service_parameters_path = app .service_parameters_path
36
+ self .host = "127.0.0.1"
37
+ self .server = make_server (self .host , 0 , app )
27
38
port = self .server .port
28
- self .url = f"http://{ host } :{ port } "
39
+ self .url = f"http://{ self .host } :{ port } "
40
+ app .url = self .url
29
41
self .thread = threading .Thread (name = "server" , target = self .server .serve_forever )
30
42
self .thread .start ()
31
43
return self
@@ -142,3 +154,160 @@ def test_client_cli(use_lro: bool, tmp_path):
142
154
with open (receipt_path_2 , "rb" ) as f :
143
155
receipt_2 = f .read ()
144
156
assert receipt == receipt_2
157
+
158
+
159
+ def create_flask_app_oidc_server (config ):
160
+ app = Flask ("oidc_server" )
161
+
162
+ app .config .update (dict (DEBUG = True ))
163
+ app .config .update (config )
164
+
165
+ @app .route ("/.well-known/openid-configuration" , methods = ["GET" ])
166
+ def openid_configuration ():
167
+ return jsonify (
168
+ {
169
+ "issuer" : app .url ,
170
+ "jwks_uri" : f"{ app .url } /.well-known/jwks" ,
171
+ "response_types_supported" : ["id_token" ],
172
+ "claims_supported" : ["sub" , "aud" , "exp" , "iat" , "iss" ],
173
+ "id_token_signing_alg_values_supported" : app .config ["algorithms" ],
174
+ "scopes_supported" : ["openid" ],
175
+ }
176
+ )
177
+
178
+ @app .route ("/.well-known/jwks" , methods = ["GET" ])
179
+ def jwks ():
180
+ return jsonify (
181
+ {
182
+ "keys" : [
183
+ {
184
+ ** app .config ["key" ].export_public (as_dict = True ),
185
+ "use" : "sig" ,
186
+ "kid" : app .config ["key" ].thumbprint (),
187
+ }
188
+ ]
189
+ }
190
+ )
191
+
192
+ return app
193
+
194
+
195
+ def test_client_cli_token (tmp_path ):
196
+ workspace_path = tmp_path / "workspace"
197
+
198
+ claim_path = tmp_path / "claim.cose"
199
+ receipt_path = tmp_path / "claim.receipt.cbor"
200
+ entry_id_path = tmp_path / "claim.entry_id.txt"
201
+ retrieved_claim_path = tmp_path / "claim.retrieved.cose"
202
+
203
+ key = jwcrypto .jwk .JWK .generate (kty = "RSA" , size = 2048 )
204
+ algorithm = "RS256"
205
+ audience = "scitt.example.org"
206
+ subject = "repo:scitt-community/scitt-api-emulator:ref:refs/heads/main"
207
+
208
+ with Service (
209
+ {"key" : key , "algorithms" : [algorithm ]},
210
+ create_flask_app = create_flask_app_oidc_server ,
211
+ ) as oidc_service :
212
+ os .environ ["no_proxy" ] = "," .join (
213
+ os .environ .get ("no_proxy" , "" ).split ("," ) + [oidc_service .host ]
214
+ )
215
+ middleware_config_path = tmp_path / "oidc-middleware-config.json"
216
+ middleware_config_path .write_text (
217
+ json .dumps (
218
+ {
219
+ "issuers" : [oidc_service .url ],
220
+ "audience" : audience ,
221
+ "claim_schema" : {
222
+ oidc_service .url : {
223
+ "$schema" : "https://json-schema.org/draft/2020-12/schema" ,
224
+ "required" : ["sub" ],
225
+ "properties" : {
226
+ "sub" : {"type" : "string" , "enum" : [subject ]},
227
+ },
228
+ }
229
+ },
230
+ }
231
+ )
232
+ )
233
+ with Service (
234
+ {
235
+ "middleware" : OIDCAuthMiddleware ,
236
+ "middleware_config_path" : middleware_config_path ,
237
+ "tree_alg" : "CCF" ,
238
+ "workspace" : workspace_path ,
239
+ "error_rate" : 0.1 ,
240
+ "use_lro" : False ,
241
+ }
242
+ ) as service :
243
+ # create claim
244
+ command = [
245
+ "client" ,
246
+ "create-claim" ,
247
+ "--out" ,
248
+ claim_path ,
249
+ "--issuer" ,
250
+ issuer ,
251
+ "--content-type" ,
252
+ content_type ,
253
+ "--payload" ,
254
+ payload ,
255
+ ]
256
+ execute_cli (command )
257
+ assert os .path .exists (claim_path )
258
+
259
+ # submit claim without token
260
+ command = [
261
+ "client" ,
262
+ "submit-claim" ,
263
+ "--claim" ,
264
+ claim_path ,
265
+ "--out" ,
266
+ receipt_path ,
267
+ "--out-entry-id" ,
268
+ entry_id_path ,
269
+ "--url" ,
270
+ service .url ,
271
+ ]
272
+ check_error = None
273
+ try :
274
+ execute_cli (command )
275
+ except Exception as error :
276
+ check_error = error
277
+ assert check_error
278
+ assert not os .path .exists (receipt_path )
279
+ assert not os .path .exists (entry_id_path )
280
+
281
+ # create token without subject
282
+ token = jwt .encode (
283
+ {"iss" : oidc_service .url , "aud" : audience },
284
+ key .export_to_pem (private_key = True , password = None ),
285
+ algorithm = algorithm ,
286
+ headers = {"kid" : key .thumbprint ()},
287
+ )
288
+ # submit claim with token lacking subject
289
+ command += [
290
+ "--token" ,
291
+ token ,
292
+ ]
293
+ check_error = None
294
+ try :
295
+ execute_cli (command )
296
+ except Exception as error :
297
+ check_error = error
298
+ assert check_error
299
+ assert not os .path .exists (receipt_path )
300
+ assert not os .path .exists (entry_id_path )
301
+
302
+ # create token with subject
303
+ token = jwt .encode (
304
+ {"iss" : oidc_service .url , "aud" : audience , "sub" : subject },
305
+ key .export_to_pem (private_key = True , password = None ),
306
+ algorithm = algorithm ,
307
+ headers = {"kid" : key .thumbprint ()},
308
+ )
309
+ # submit claim with token containing subject
310
+ command [- 1 ] = token
311
+ execute_cli (command )
312
+ assert os .path .exists (receipt_path )
313
+ assert os .path .exists (entry_id_path )
0 commit comments