1- """minos.api_gateway.rest.handler module."""
2-
31import logging
42from typing import (
53 Any ,
4+ Optional ,
65)
76
87from aiohttp import (
1514 URL ,
1615)
1716
17+ from .exceptions import (
18+ InvalidAuthenticationException ,
19+ NoTokenException ,
20+ )
21+
1822logger = logging .getLogger (__name__ )
1923
2024
2125async def orchestrate (request : web .Request ) -> web .Response :
2226 """ Orchestrate discovery and microservice call """
23- discovery_host = request .app ["config" ].discovery .connection . host
24- discovery_port = request .app ["config" ].discovery .connection . port
27+ discovery_host = request .app ["config" ].discovery .host
28+ discovery_port = request .app ["config" ].discovery .port
2529
2630 verb = request .method
2731 url = f"/{ request .match_info ['endpoint' ]} "
2832
2933 discovery_data = await discover (discovery_host , int (discovery_port ), "/microservices" , verb , url )
3034
31- microservice_response = await call (** discovery_data , original_req = request )
35+ user = await get_user (request )
36+
37+ microservice_response = await call (** discovery_data , original_req = request , user = user )
3238 return microservice_response
3339
3440
41+ async def get_user (request : web .Request ) -> Optional [str ]:
42+ """Get The user identifier if it is available.
43+
44+ :param request: The external request.
45+ :return: An string value containing the user identifier or ``None`` if no user information is available.
46+ """
47+ auth = request .app ["config" ].rest .auth
48+ if auth is None or not auth .enabled :
49+ return None
50+
51+ try :
52+ await get_token (request )
53+ except NoTokenException :
54+ return None
55+
56+ try :
57+ original_headers = dict (request .headers .copy ())
58+ return await authenticate (auth .host , auth .port , auth .method , auth .path , original_headers )
59+ except InvalidAuthenticationException :
60+ return None
61+
62+
3563async def discover (host : str , port : int , path : str , verb : str , endpoint : str ) -> dict [str , Any ]:
3664 """Call discovery service and get microservice connection data.
3765
@@ -59,26 +87,33 @@ async def discover(host: str, port: int, path: str, verb: str, endpoint: str) ->
5987
6088
6189# noinspection PyUnusedLocal
62- async def call (address : str , port : int , original_req : web .Request , ** kwargs ) -> web .Response :
90+ async def call (address : str , port : int , original_req : web .Request , user : Optional [ str ], ** kwargs ) -> web .Response :
6391 """Call microservice (redirect the original call)
6492
6593 :param address: The ip of the microservices.
6694 :param port: The port of the microservice.
6795 :param original_req: The original request.
6896 :param kwargs: Additional named arguments.
97+ :param user: User that makes the request
6998 :return: The web response to be retrieved to the client.
7099 """
71100
72- headers = original_req .headers
101+ headers = original_req .headers .copy ()
102+ if user is not None :
103+ headers ["User" ] = user
104+ else : # Enforce that the 'User' entry is only generated by the auth system.
105+ # noinspection PyTypeChecker
106+ headers .pop ("User" , None )
107+
73108 url = original_req .url .with_scheme ("http" ).with_host (address ).with_port (port )
74109 method = original_req .method
75110 content = await original_req .text ()
76111
77112 logger .info (f"Redirecting { method !r} request to { url !r} ..." )
78113
79114 try :
80- async with ClientSession (headers = headers ) as session :
81- async with session .request (method = method , url = url , data = content ) as response :
115+ async with ClientSession () as session :
116+ async with session .request (headers = headers , method = method , url = url , data = content ) as response :
82117 return await _clone_response (response )
83118 except ClientConnectorError :
84119 raise web .HTTPServiceUnavailable (text = "The requested endpoint is not available." )
@@ -89,3 +124,30 @@ async def _clone_response(response: ClientResponse) -> web.Response:
89124 return web .Response (
90125 body = await response .read (), status = response .status , reason = response .reason , headers = response .headers ,
91126 )
127+
128+
129+ async def authenticate (host : str , port : str , method : str , path : str , authorization_headers : dict [str , str ]) -> str :
130+ authentication_url = URL (f"http://{ host } :{ port } { path } " )
131+ authentication_method = method
132+ logger .info ("Authenticating request..." )
133+
134+ try :
135+ async with ClientSession (headers = authorization_headers ) as session :
136+ async with session .request (method = authentication_method , url = authentication_url ) as response :
137+ if response .ok :
138+ jwt_payload = await response .json ()
139+ return jwt_payload ["sub" ]
140+ else :
141+ raise InvalidAuthenticationException
142+ except (ClientConnectorError , web .HTTPError ):
143+ raise InvalidAuthenticationException
144+
145+
146+ async def get_token (request : web .Request ) -> str :
147+ headers = request .headers
148+ if "Authorization" in headers and "Bearer" in headers ["Authorization" ]:
149+ parts = headers ["Authorization" ].split ()
150+ if len (parts ) == 2 :
151+ return parts [1 ]
152+
153+ raise NoTokenException
0 commit comments