Skip to content

Commit 85e7312

Browse files
authored
Merge pull request #53 from Clariteia/issue-45-call-auth-microservice
#45 - call auth microservice
2 parents d6bfc10 + f0c7e2c commit 85e7312

File tree

4 files changed

+132
-15
lines changed

4 files changed

+132
-15
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
class InvalidAuthenticationException(Exception):
2+
pass
3+
4+
5+
class NoTokenException(Exception):
6+
pass

minos/api_gateway/rest/handler.py

Lines changed: 57 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@
1515
URL,
1616
)
1717

18+
from minos.api_gateway.rest.exceptions import (
19+
InvalidAuthenticationException,
20+
NoTokenException,
21+
)
22+
23+
ANONYMOUS = "Anonymous"
24+
1825
logger = logging.getLogger(__name__)
1926

2027

@@ -28,10 +35,27 @@ async def orchestrate(request: web.Request) -> web.Response:
2835

2936
discovery_data = await discover(discovery_host, int(discovery_port), "/microservices", verb, url)
3037

31-
microservice_response = await call(**discovery_data, original_req=request)
38+
user = await get_user(request)
39+
40+
microservice_response = await call(**discovery_data, original_req=request, user=user)
3241
return microservice_response
3342

3443

44+
async def get_user(request) -> str:
45+
try:
46+
await get_token(request)
47+
except NoTokenException:
48+
return ANONYMOUS
49+
else:
50+
try:
51+
original_headers = dict(request.headers.copy())
52+
user_uuid = await authenticate("localhost", "8082", "POST", "token", original_headers)
53+
except InvalidAuthenticationException:
54+
return ANONYMOUS
55+
else:
56+
return user_uuid
57+
58+
3559
async def discover(host: str, port: int, path: str, verb: str, endpoint: str) -> dict[str, Any]:
3660
"""Call discovery service and get microservice connection data.
3761
@@ -59,25 +83,19 @@ async def discover(host: str, port: int, path: str, verb: str, endpoint: str) ->
5983

6084

6185
# noinspection PyUnusedLocal
62-
async def call(address: str, port: int, original_req: web.Request, **kwargs) -> web.Response:
86+
async def call(address: str, port: int, original_req: web.Request, user: str, **kwargs) -> web.Response:
6387
"""Call microservice (redirect the original call)
6488
6589
:param address: The ip of the microservices.
6690
:param port: The port of the microservice.
6791
:param original_req: The original request.
6892
:param kwargs: Additional named arguments.
93+
:param user: User that makes the request
6994
:return: The web response to be retrieved to the client.
7095
"""
7196

7297
headers = original_req.headers.copy()
73-
if "Authorization" in headers and "Bearer" in headers["Authorization"]:
74-
parts = headers["Authorization"].split()
75-
if len(parts) == 2:
76-
headers["User"] = parts[1]
77-
else:
78-
headers["User"] = "None"
79-
else:
80-
headers["User"] = "None"
98+
headers["User"] = user
8199

82100
url = original_req.url.with_scheme("http").with_host(address).with_port(port)
83101
method = original_req.method
@@ -86,8 +104,8 @@ async def call(address: str, port: int, original_req: web.Request, **kwargs) ->
86104
logger.info(f"Redirecting {method!r} request to {url!r}...")
87105

88106
try:
89-
async with ClientSession(headers=headers) as session:
90-
async with session.request(method=method, url=url, data=content) as response:
107+
async with ClientSession() as session:
108+
async with session.request(headers=headers, method=method, url=url, data=content) as response:
91109
return await _clone_response(response)
92110
except ClientConnectorError:
93111
raise web.HTTPServiceUnavailable(text="The requested endpoint is not available.")
@@ -98,3 +116,30 @@ async def _clone_response(response: ClientResponse) -> web.Response:
98116
return web.Response(
99117
body=await response.read(), status=response.status, reason=response.reason, headers=response.headers,
100118
)
119+
120+
121+
async def authenticate(address: str, port: str, method: str, path: str, authorization_headers: dict[str, str]) -> str:
122+
authentication_url = URL(f"http://{address}:{port}/{path}")
123+
authentication_method = method
124+
logger.info("Authenticating request...")
125+
126+
try:
127+
async with ClientSession(headers=authorization_headers) as session:
128+
async with session.request(method=authentication_method, url=authentication_url) as response:
129+
if response.ok:
130+
jwt_payload = await response.json()
131+
return jwt_payload["sub"]
132+
else:
133+
raise InvalidAuthenticationException
134+
except (ClientConnectorError, web.HTTPError):
135+
raise InvalidAuthenticationException
136+
137+
138+
async def get_token(request: web.Request) -> str:
139+
headers = request.headers
140+
if "Authorization" in headers and "Bearer" in headers["Authorization"]:
141+
parts = headers["Authorization"].split()
142+
if len(parts) == 2:
143+
return parts[1]
144+
145+
raise NoTokenException

tests/config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ rest:
44
endpoints: []
55
discovery:
66
host: localhost
7-
port: 55656
7+
port: 5567
88
path: ""
99
endpoints: []
1010
db:

tests/test_api_gateway/test_rest/test_authentication.py

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
from unittest import (
33
mock,
44
)
5+
from uuid import (
6+
uuid4,
7+
)
58

69
from aiohttp.test_utils import (
710
AioHTTPTestCase,
@@ -22,7 +25,7 @@
2225
)
2326

2427

25-
class TestApiGatewayAuth(AioHTTPTestCase):
28+
class TestApiGatewayAuthentication(AioHTTPTestCase):
2629
CONFIG_FILE_PATH = BASE_PATH / "config.yml"
2730

2831
@mock.patch.dict(os.environ, {"API_GATEWAY_CORS_ENABLED": "true"})
@@ -42,13 +45,18 @@ def setUp(self) -> None:
4245
)
4346
self.microservice.add_json_response("/order", "Microservice call correct!!!", methods=("POST",))
4447

48+
self.authentication_service = MockServer(host="localhost", port=8082)
49+
self.authentication_service.add_json_response("/token", {"sub": uuid4()}, methods=("POST",))
50+
4551
self.discovery.start()
4652
self.microservice.start()
53+
self.authentication_service.start()
4754
super().setUp()
4855

4956
def tearDown(self) -> None:
5057
self.discovery.shutdown_server()
5158
self.microservice.shutdown_server()
59+
self.authentication_service.shutdown_server()
5260
super().tearDown()
5361

5462
async def get_application(self):
@@ -65,6 +73,7 @@ async def get_application(self):
6573
async def test_auth_headers(self):
6674
url = "/order"
6775
headers = {"Authorization": "Bearer test_token"}
76+
6877
response = await self.client.request("POST", url, headers=headers)
6978

7079
self.assertEqual(200, response.status)
@@ -73,7 +82,64 @@ async def test_auth_headers(self):
7382
@unittest_run_loop
7483
async def test_wrong_auth_headers(self):
7584
url = "/order"
76-
headers = {"Authorization": "Bearer"}
85+
headers = {"Authorization": "Bearer"} # Missing token
86+
response = await self.client.request("POST", url, headers=headers)
87+
88+
self.assertEqual(200, response.status)
89+
self.assertIn("Microservice call correct!!!", await response.text())
90+
91+
@unittest_run_loop
92+
async def test_request_has_token(self):
93+
url = "/order"
94+
headers = {"Authorization": "Bearer"} # Missing token
95+
response = await self.client.request("POST", url, headers=headers)
96+
97+
self.assertEqual(200, response.status)
98+
self.assertIn("Microservice call correct!!!", await response.text())
99+
100+
101+
class TestAuthUnreachable(AioHTTPTestCase):
102+
CONFIG_FILE_PATH = BASE_PATH / "config.yml"
103+
104+
def setUp(self) -> None:
105+
self.config = MinosConfig(self.CONFIG_FILE_PATH)
106+
107+
self.discovery = MockServer(
108+
host=self.config.discovery.connection.host, port=self.config.discovery.connection.port,
109+
)
110+
self.discovery.add_json_response(
111+
"/microservices", {"address": "localhost", "port": "5568", "status": True},
112+
)
113+
114+
self.microservice = MockServer(host="localhost", port=5568)
115+
self.microservice.add_json_response(
116+
"/order/5", "Microservice call correct!!!", methods=("GET", "PUT", "PATCH", "DELETE",)
117+
)
118+
self.microservice.add_json_response("/order", "Microservice call correct!!!", methods=("POST",))
119+
120+
self.discovery.start()
121+
self.microservice.start()
122+
super().setUp()
123+
124+
def tearDown(self) -> None:
125+
self.discovery.shutdown_server()
126+
self.microservice.shutdown_server()
127+
super().tearDown()
128+
129+
async def get_application(self):
130+
"""
131+
Override the get_app method to return your application.
132+
"""
133+
rest_service = ApiGatewayRestService(
134+
address=self.config.rest.connection.host, port=self.config.rest.connection.port, config=self.config
135+
)
136+
137+
return await rest_service.create_application()
138+
139+
@unittest_run_loop
140+
async def test_auth_unreachable(self):
141+
url = "/order"
142+
headers = {"Authorization": "Bearer test_token"}
77143
response = await self.client.request("POST", url, headers=headers)
78144

79145
self.assertEqual(200, response.status)

0 commit comments

Comments
 (0)