2323from gen3workflow .routes .system import get_status
2424
2525
26- root_router = APIRouter ()
26+ s3_root_router = APIRouter ()
2727s3_router = APIRouter (prefix = "/s3" )
2828
2929
30- def get_access_token ( headers : Headers ) -> Tuple [ str , str ] :
30+ async def set_access_token_and_get_user_id ( auth : Auth , headers : Headers ) -> str :
3131 """
32- Extract the user's access token and (in the case of a client token ) the user's ID, which
33- should have been provided as the key ID, from the Authorization header in one of the two
34- following expected formats:
32+ Extract the user's access token and (in some cases ) the user's ID, which should have been
33+ provided as the access key ID, from the Authorization header in one of the two following
34+ expected formats:
3535 1. Key ID set by the python boto3 AWS client: `AWS4-HMAC-SHA256 Credential=<key ID>/<date>/
36- <region>/<service>/aws4_request, SignedHeaders=<>, Signature=<>`
36+ <region>/<service>/aws4_request, SignedHeaders=<>, Signature=<>`
3737 2. Key ID set by Funnel GenericS3 through the Minio-go client: `AWS <key ID>:<>`
38+ Return the user's ID extracted from the key ID or from the decoded token. Also set the provided
39+ `auth` instance's `bearer_token` to the extracted access token.
3840
3941 Args:
42+ auth (Auth): Gen3Workflow auth instance
4043 headers (Headers): request headers
4144
4245 Returns:
43- (str, str): the user's access token or "" if not found, and the user's ID if the token is
44- a client_credentials token
46+ str: the user's ID
4547 """
4648 # TODO unit tests for this function
4749 auth_header = headers .get ("authorization" )
4850 if not auth_header :
49- return "" , ""
51+ return ""
5052 if auth_header .lower ().startswith ("bearer" ):
5153 err_msg = f"Bearer tokens in the authorization header are not supported by this endpoint, which expects signed S3 requests. The recommended way to use this endpoint is to use the AWS SDK or CLI"
5254 logger .error (err_msg )
5355 raise HTTPException (HTTP_401_UNAUTHORIZED , err_msg )
56+
5457 try :
5558 if "Credential=" in auth_header : # format 1 (see docstring)
56- access_token = auth_header .split ("Credential=" )[1 ].split ("/" )[0 ]
57- user_id = None
59+ access_key_id = auth_header .split ("Credential=" )[1 ].split ("/" )[0 ]
5860 else : # format 2 (see docstring)
5961 access_key_id = auth_header .split ("AWS " )[1 ]
6062 access_key_id = ":" .join (access_key_id .split (":" )[:- 1 ])
61- access_token , user_id = access_key_id .split (";userId=" )
62- return access_token , user_id
6363 except Exception as e :
6464 traceback .print_exc ()
6565 logger .error (
6666 f"Unexpected format; unable to extract access token from authorization header: { e } "
6767 )
68- return "" , ""
68+ return ""
69+
70+ if ";userId=" in access_key_id :
71+ access_token , user_id = access_key_id .split (";userId=" )
72+ # TODO assert it's a client token not linked to a user
73+ else :
74+ access_token = access_key_id
75+ auth .bearer_token = HTTPAuthorizationCredentials (
76+ scheme = "bearer" , credentials = access_token
77+ )
78+ token_claims = await auth .get_token_claims ()
79+ user_id = token_claims .get ("sub" )
80+
81+ return user_id
6982
7083
7184def get_signature_key (key : str , date : str , region_name : str , service_name : str ) -> str :
@@ -85,7 +98,7 @@ def get_signature_key(key: str, date: str, region_name: str, service_name: str)
8598 return key_signing
8699
87100
88- @root_router .api_route (
101+ @s3_root_router .api_route (
89102 "/{path:path}" ,
90103 methods = ["GET" , "POST" , "PUT" , "DELETE" , "OPTIONS" , "PATCH" , "TRACE" , "HEAD" ],
91104)
@@ -103,24 +116,17 @@ async def s3_endpoint(path: str, request: Request):
103116 not support S3 endpoints with a path, such as the Minio-go S3 client.
104117 """
105118
106- # because this endpoint is exposed at root, if the path is empty, assume the user is not trying
107- # to reach the S3 endpoint and redirect to the status endpoint instead
119+ # because this endpoint is exposed at root, if the GET path is empty, assume the user is not
120+ # trying to reach the S3 endpoint and redirect to the status endpoint instead
108121 if request .method == "GET" and not path :
109122 return await get_status (request )
110123
111- # extract the user 's access token from the request headers, and ensure the user has access
112- # to run workflows
124+ # extract the caller 's access token from the request headers, and ensure the caller ( user, or
125+ # client acting on behalf of the user) has access to run workflows
113126 auth = Auth (api_request = request )
114- access_token , user_id = get_access_token (request .headers )
115- if user_id :
116- pass # TODO assert it's a client token not linked to a user, and check authz
117- else :
118- auth .bearer_token = HTTPAuthorizationCredentials (
119- scheme = "bearer" , credentials = access_token
120- )
121- await auth .authorize ("create" , ["/services/workflow/gen3-workflow/tasks" ])
122- token_claims = await auth .get_token_claims ()
123- user_id = token_claims .get ("sub" )
127+ user_id = await set_access_token_and_get_user_id (auth , request .headers )
128+ # TODO client token unit tests, including authz
129+ await auth .authorize ("create" , ["/services/workflow/gen3-workflow/tasks" ])
124130
125131 # get the name of the user's bucket and ensure the user is making a call to their own bucket
126132 logger .info (f"Incoming S3 request from user '{ user_id } ': '{ request .method } { path } '" )
0 commit comments