55import time
66
77import pytest
8+ from fastapi import HTTPException , Request
9+ from fastapi .security import HTTPAuthorizationCredentials
810
911from app .auth import (
1012 InvalidWsiToken ,
13+ require_user ,
1114 source_digest ,
1215 validate_wsi_auth_configuration ,
1316 validate_wsi_token ,
1619
1720def make_token (secret : str , ** claims ) -> str :
1821 def encode (value ):
19- return base64 .urlsafe_b64encode (json .dumps (value ).encode ()).rstrip (b"=" ).decode ()
22+ return (
23+ base64 .urlsafe_b64encode (json .dumps (value ).encode ()).rstrip (b"=" ).decode ()
24+ )
2025
2126 header = encode ({"alg" : "HS256" , "typ" : "JWT" })
2227 payload = encode (claims )
2328 signing_input = f"{ header } .{ payload } " .encode ()
24- signature = base64 .urlsafe_b64encode (
25- hmac .new (secret .encode (), signing_input , hashlib .sha256 ).digest ()
26- ).rstrip (b"=" ).decode ()
29+ signature = (
30+ base64 .urlsafe_b64encode (
31+ hmac .new (secret .encode (), signing_input , hashlib .sha256 ).digest ()
32+ )
33+ .rstrip (b"=" )
34+ .decode ()
35+ )
2736 return f"{ header } .{ payload } .{ signature } "
2837
2938
3039def make_raw_token (secret : str , header , payload ) -> str :
3140 def encode (value ):
32- return base64 .urlsafe_b64encode (json .dumps (value ).encode ()).rstrip (b"=" ).decode ()
41+ return (
42+ base64 .urlsafe_b64encode (json .dumps (value ).encode ()).rstrip (b"=" ).decode ()
43+ )
3344
3445 encoded_header = encode (header )
3546 encoded_payload = encode (payload )
3647 signing_input = f"{ encoded_header } .{ encoded_payload } " .encode ()
37- signature = base64 .urlsafe_b64encode (
38- hmac .new (secret .encode (), signing_input , hashlib .sha256 ).digest ()
39- ).rstrip (b"=" ).decode ()
48+ signature = (
49+ base64 .urlsafe_b64encode (
50+ hmac .new (secret .encode (), signing_input , hashlib .sha256 ).digest ()
51+ )
52+ .rstrip (b"=" )
53+ .decode ()
54+ )
4055 return f"{ encoded_header } .{ encoded_payload } .{ signature } "
4156
4257
@@ -62,9 +77,12 @@ def valid_claims(**overrides):
6277
6378def test_valid_wsi_token ():
6479 secret = "s" * 32
65- assert validate_wsi_token (
66- make_token (secret , ** valid_claims ()), secret , "cbioportal-wsi"
67- )["sub" ] == "user@example.org"
80+ assert (
81+ validate_wsi_token (
82+ make_token (secret , ** valid_claims ()), secret , "cbioportal-wsi"
83+ )["sub" ]
84+ == "user@example.org"
85+ )
6886
6987
7088@pytest .mark .parametrize (
@@ -82,11 +100,14 @@ def test_invalid_wsi_auth_configuration_is_rejected(secret, audience, max_ttl):
82100 validate_wsi_auth_configuration (secret , audience , max_ttl )
83101
84102
85- @pytest .mark .parametrize ("change" , [
86- {"scope" : "wsi:write" },
87- {"aud" : "other-service" },
88- {"exp" : int (time .time ()) - 1 },
89- ])
103+ @pytest .mark .parametrize (
104+ "change" ,
105+ [
106+ {"scope" : "wsi:write" },
107+ {"aud" : "other-service" },
108+ {"exp" : int (time .time ()) - 1 },
109+ ],
110+ )
90111def test_invalid_claims_are_rejected (change ):
91112 secret = "s" * 32
92113 claims = valid_claims (** change )
@@ -114,13 +135,16 @@ def test_non_object_header_and_payload_are_rejected():
114135 )
115136
116137
117- @pytest .mark .parametrize ("change" , [
118- {"study_id" : "" },
119- {"image_id" : "" },
120- {"tile_source_sha256" : "" },
121- {"thumbnail_width" : 0 },
122- {"exp" : int (time .time ()) + 1000 },
123- ])
138+ @pytest .mark .parametrize (
139+ "change" ,
140+ [
141+ {"study_id" : "" },
142+ {"image_id" : "" },
143+ {"tile_source_sha256" : "" },
144+ {"thumbnail_width" : 0 },
145+ {"exp" : int (time .time ()) + 1000 },
146+ ],
147+ )
124148def test_source_bound_claims_and_max_ttl_are_required (change ):
125149 secret = "s" * 32
126150 with pytest .raises (InvalidWsiToken ):
@@ -169,3 +193,23 @@ def test_annotation_capability_requires_both_annotation_scopes():
169193 "cbioportal-wsi" ,
170194 required_scopes = {"annotations:read" , "annotations:write" },
171195 )
196+
197+
198+ @pytest .mark .asyncio
199+ async def test_annotation_auth_rejects_non_capability_tokens ():
200+ request = Request (
201+ {
202+ "type" : "http" ,
203+ "method" : "GET" ,
204+ "path" : "/annotations" ,
205+ "headers" : [],
206+ }
207+ )
208+ credentials = HTTPAuthorizationCredentials (
209+ scheme = "Bearer" , credentials = "generic-keycloak-token"
210+ )
211+
212+ with pytest .raises (HTTPException ) as error :
213+ await require_user (request , credentials )
214+
215+ assert error .value .status_code == 401
0 commit comments