Merge "Update chart to support the deployment without persistent database"
[pti/o2.git] / o2common / authmw / authmiddleware.py
1 # Copyright (C) 2022 Wind River Systems, Inc.
2 #
3 #  Licensed under the Apache License, Version 2.0 (the "License");
4 #  you may not use this file except in compliance with the License.
5 #  You may obtain a copy of the License at
6 #
7 #      http://www.apache.org/licenses/LICENSE-2.0
8 #
9 #  Unless required by applicable law or agreed to in writing, software
10 #  distributed under the License is distributed on an "AS IS" BASIS,
11 #  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 #  See the License for the specific language governing permissions and
13 #  limitations under the License.
14
15 from werkzeug.wrappers import Request, Response
16 from o2common.helper import o2logging
17 from o2common.authmw.authprov import auth_definer
18 from flask_restx._http import HTTPStatus
19 import json
20
21 logger = o2logging.get_logger(__name__)
22
23
24 class AuthRequiredExp(Exception):
25     def __init__(self, value):
26         self.value = value
27
28     def dictize(self):
29         return {
30             'WWW-Authenticate': '{}'.format(self.value)}
31
32
33 class AuthProblemDetails():
34     def __init__(self, code: int, detail: str, path: str,
35                  title=None, instance=None
36                  ) -> None:
37         self.status = code
38         self.detail = detail
39         self.type = path
40         self.title = title if title is not None else self.getTitle(code)
41         self.instance = instance if instance is not None else []
42
43     def getTitle(self, code):
44         return HTTPStatus(code).phrase
45
46     def serialize(self):
47         details = {}
48         for key in dir(self):
49             if key == 'ns' or key.startswith('__') or \
50                     callable(getattr(self, key)):
51                 continue
52             else:
53                 details[key] = getattr(self, key)
54         return json.dumps(details, indent=True)
55
56
57 class AuthFailureExp(Exception):
58     def __init__(self, value):
59         self.value = value
60
61     def dictize(self):
62         return {
63             'WWW-Authenticate': '{}'.format(self.value)}
64
65
66 def _response_wrapper(environ, start_response, header, detail):
67     res = Response(headers=header,
68                    mimetype='application/json', status=401, response=detail)
69     return res(environ, start_response)
70
71
72 def _internal_err_response_wrapper(environ, start_response, detail):
73     res = Response(mimetype='application/json', status=500, response=detail)
74     return res(environ, start_response)
75
76
77 class authmiddleware():
78
79     '''
80     Auth WSGI middleware
81     '''
82
83     def __init__(self, app):
84         self.app = app
85
86     def __call__(self, environ, start_response):
87         logger.debug(__name__ + 'authentication middleware')
88         req = Request(environ, populate_request=True, shallow=True)
89         auth_token = None
90         try:
91             auth_header = req.headers.get('Authorization', None)
92             if auth_header:
93                 auth_token = auth_header.split(" ")[1]
94
95                 ad = auth_definer('oauth')
96                 # invoke underlying auth mdw to make k8s/keystone api
97                 ret = ad.authenticate(auth_token)
98                 if ret is True:
99                     logger.debug(
100                         "auth success with oauth token: " + auth_token)
101                     try:
102                         return self.app(environ, start_response)
103                     except Exception as ex:
104                         logger.error(
105                             'Internal exception happend \
106                             ed {}'.format(str(ex)), exc_info=True)
107                         prb = AuthProblemDetails(
108                             500, 'Internal error.', req.path)
109                         return \
110                             _internal_err_response_wrapper(
111                                 environ,
112                                 start_response, prb.serialize())
113                 else:
114                     raise AuthFailureExp(
115                         'Bearer realm="Authentication Failed"')
116             else:
117                 raise AuthRequiredExp('Bearer realm="Authentication Required"')
118         except AuthRequiredExp as ex:
119             prb = AuthProblemDetails(401, ex.value, req.path)
120             return _response_wrapper(environ, start_response,
121                                      ex.dictize(), prb.serialize())
122         except AuthFailureExp as ex:
123             prb = AuthProblemDetails(401, ex.value, req.path)
124             return _response_wrapper(environ, start_response,
125                                      ex.dictize(), prb.serialize())
126         except Exception as ex:
127             if auth_token:
128                 logger.error('Internal exception happended {}'.format(
129                     str(ex)), exc_info=True)
130                 prb = AuthProblemDetails(500, 'Internal error.', req.path)
131                 return \
132                     _internal_err_response_wrapper(
133                         environ, start_response, prb.serialize())
134             else:
135                 logger.debug('Auth token missing or not obtained.')
136                 ex = AuthRequiredExp('Bearer realm="Authentication Required"')
137                 prb = AuthProblemDetails(401, ex.value, req.path)
138                 return _response_wrapper(environ, start_response,
139                                          ex.dictize(), prb.serialize())