Merge "OAuth2 support"
[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 import json
16 from flask_restx._http import HTTPStatus
17 from werkzeug.wrappers import Request, Response
18
19 from o2common.authmw.authprov import auth_definer
20 from o2common.authmw.exceptions import AuthRequiredExp
21 from o2common.authmw.exceptions import AuthFailureExp
22 from o2common.helper import o2logging
23
24 logger = o2logging.get_logger(__name__)
25
26
27 class AuthProblemDetails():
28     def __init__(self, code: int, detail: str, path: str,
29                  title=None, instance=None
30                  ) -> None:
31         self.status = code
32         self.detail = detail
33         self.type = path
34         self.title = title if title is not None else self.getTitle(code)
35         self.instance = instance if instance is not None else []
36
37     def getTitle(self, code):
38         return HTTPStatus(code).phrase
39
40     def serialize(self):
41         details = {}
42         for key in dir(self):
43             if key == 'ns' or key.startswith('__') or \
44                     callable(getattr(self, key)):
45                 continue
46             else:
47                 details[key] = getattr(self, key)
48         return json.dumps(details, indent=True)
49
50
51 def _response_wrapper(environ, start_response, header, detail):
52     res = Response(headers=header,
53                    mimetype='application/json', status=401, response=detail)
54     return res(environ, start_response)
55
56
57 def _internal_err_response_wrapper(environ, start_response, detail):
58     res = Response(mimetype='application/json', status=500, response=detail)
59     return res(environ, start_response)
60
61
62 class authmiddleware():
63     '''
64     Auth WSGI middleware
65     '''
66
67     def __init__(self, app):
68         self.app = app
69
70     def __call__(self, environ, start_response):
71         logger.debug(__name__ + 'authentication middleware')
72         req = Request(environ, populate_request=True, shallow=True)
73         auth_token = None
74         try:
75             auth_header = req.headers.get('Authorization', None)
76             if auth_header:
77                 auth_token = auth_header.split(" ")[1]
78
79                 ad = auth_definer('oauth')
80                 # invoke underlying auth mdw to make k8s/keystone api
81                 ret = ad.authenticate(auth_token)
82                 if ret is True:
83                     logger.debug(
84                         "auth success with oauth token: " + auth_token)
85                     try:
86                         return self.app(environ, start_response)
87                     except Exception as ex:
88                         logger.error(
89                             'Internal exception happend \
90                             ed {}'.format(str(ex)), exc_info=True)
91                         prb = AuthProblemDetails(
92                             500, 'Internal error.', req.path)
93                         return \
94                             _internal_err_response_wrapper(
95                                 environ,
96                                 start_response, prb.serialize())
97                 else:
98                     raise AuthFailureExp(
99                         'Bearer realm="Authentication Failed"')
100             else:
101                 raise AuthRequiredExp('Bearer realm="Authentication Required"')
102         except AuthRequiredExp as ex:
103             prb = AuthProblemDetails(401, ex.value, req.path)
104             return _response_wrapper(environ, start_response,
105                                      ex.dictize(), prb.serialize())
106         except AuthFailureExp as ex:
107             prb = AuthProblemDetails(401, ex.value, req.path)
108             return _response_wrapper(environ, start_response,
109                                      ex.dictize(), prb.serialize())
110         except Exception as ex:
111             if auth_token:
112                 logger.error('Internal exception happended {}'.format(
113                     str(ex)), exc_info=True)
114                 prb = AuthProblemDetails(500, 'Internal error.', req.path)
115                 return \
116                     _internal_err_response_wrapper(
117                         environ, start_response, prb.serialize())
118             else:
119                 logger.debug('Auth token missing or not obtained.')
120                 ex = AuthRequiredExp('Bearer realm="Authentication Required"')
121                 prb = AuthProblemDetails(401, ex.value, req.path)
122                 return _response_wrapper(environ, start_response,
123                                          ex.dictize(), prb.serialize())