]> git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/dashboard/cherrypy_backports.py
d/control: depend on python3-yaml for ceph-mgr
[ceph.git] / ceph / src / pybind / mgr / dashboard / cherrypy_backports.py
1 # -*- coding: utf-8 -*-
2 """
3 Copyright © 2004-2019, CherryPy Team (team@cherrypy.org)
4
5 All rights reserved.
6
7 * * *
8
9 Redistribution and use in source and binary forms, with or without
10 modification, are permitted provided that the following conditions are met:
11
12 * Redistributions of source code must retain the above copyright notice, this
13 list of conditions and the following disclaimer.
14
15 * Redistributions in binary form must reproduce the above copyright notice,
16 this list of conditions and the following disclaimer in the documentation
17 and/or other materials provided with the distribution.
18
19 * Neither the name of CherryPy nor the names of its
20 contributors may be used to endorse or promote products derived from
21 this software without specific prior written permission.
22
23 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
24 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
26 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
27 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
29 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
30 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
31 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
32 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 """
34
35 from distutils.version import StrictVersion
36
37 # The SSL code in CherryPy 3.5.0 is buggy. It was fixed long ago,
38 # but 3.5.0 is still shipping in major linux distributions
39 # (Fedora 27, Ubuntu Xenial), so we must monkey patch it to get SSL working.
40
41
42 def patch_http_connection_init(v):
43 # It was fixed in 3.7.0. Exact lower bound version is probably earlier,
44 # but 3.5.0 is what this monkey patch is tested on.
45 if StrictVersion("3.5.0") <= v < StrictVersion("3.7.0"):
46 from cherrypy.wsgiserver.wsgiserver2 import \
47 HTTPConnection, CP_fileobject
48
49 def fixed_init(hc_self, server, sock, makefile=CP_fileobject):
50 hc_self.server = server
51 hc_self.socket = sock
52 hc_self.rfile = makefile(sock, "rb", hc_self.rbufsize)
53 hc_self.wfile = makefile(sock, "wb", hc_self.wbufsize)
54 hc_self.requests_seen = 0
55
56 HTTPConnection.__init__ = fixed_init
57
58
59 # When the CherryPy server in 3.2.2 (and later) starts it attempts to verify
60 # that the ports its listening on are in fact bound. When using the any address
61 # "::" it tries both ipv4 and ipv6, and in some environments (e.g. kubernetes)
62 # ipv6 isn't yet configured / supported and CherryPy throws an uncaught
63 # exception.
64 def skip_wait_for_occupied_port(v):
65 # the issue was fixed in 3.2.3. it's present in 3.2.2 (current version on
66 # centos:7) and back to at least 3.0.0.
67 if StrictVersion("3.1.2") <= v < StrictVersion("3.2.3"):
68 # https://github.com/cherrypy/cherrypy/issues/1100
69 from cherrypy.process import servers
70 servers.wait_for_occupied_port = lambda host, port: None
71
72
73 # cherrypy.wsgiserver was extracted wsgiserver into cheroot in cherrypy v9.0.0
74 def patch_builtin_ssl_wrap(v, new_wrap):
75 if v < StrictVersion("9.0.0"):
76 from cherrypy.wsgiserver.ssl_builtin import BuiltinSSLAdapter as builtin_ssl
77 else:
78 from cheroot.ssl.builtin import BuiltinSSLAdapter as builtin_ssl
79 builtin_ssl.wrap = new_wrap(builtin_ssl.wrap)
80
81
82 def accept_exceptions_from_builtin_ssl(v):
83 # the fix was included by cheroot v5.2.0, which was included by cherrypy
84 # 10.2.0.
85 if v < StrictVersion("10.2.0"):
86 # see https://github.com/cherrypy/cheroot/pull/4
87 import ssl
88
89 def accept_ssl_errors(func):
90 def wrapper(self, sock):
91 try:
92 return func(self, sock)
93 except ssl.SSLError as e:
94 if e.errno == ssl.SSL_ERROR_SSL:
95 # Check if it's one of the known errors
96 # Errors that are caught by PyOpenSSL, but thrown by
97 # built-in ssl
98 _block_errors = ('unknown protocol', 'unknown ca',
99 'unknown_ca', 'inappropriate fallback',
100 'wrong version number',
101 'no shared cipher', 'certificate unknown',
102 'ccs received early')
103 for error_text in _block_errors:
104 if error_text in e.args[1].lower():
105 # Accepted error, let's pass
106 return None, {}
107 raise
108 return wrapper
109 patch_builtin_ssl_wrap(v, accept_ssl_errors)
110
111
112 def accept_socket_error_0(v):
113 # see https://github.com/cherrypy/cherrypy/issues/1618
114 try:
115 import cheroot
116 cheroot_version = cheroot.__version__
117 except ImportError:
118 pass
119
120 if v < StrictVersion("9.0.0") or cheroot_version < StrictVersion("6.5.5"):
121 import six
122 if six.PY3:
123 generic_socket_error = OSError
124 else:
125 import socket
126 generic_socket_error = socket.error
127
128 def accept_socket_error_0(func):
129 def wrapper(self, sock):
130 try:
131 return func(self, sock)
132 except generic_socket_error as e:
133 """It is unclear why exactly this happens.
134
135 It's reproducible only with openssl>1.0 and stdlib ``ssl`` wrapper.
136 In CherryPy it's triggered by Checker plugin, which connects
137 to the app listening to the socket port in TLS mode via plain
138 HTTP during startup (from the same process).
139
140 Ref: https://github.com/cherrypy/cherrypy/issues/1618
141 """
142 import ssl
143 is_error0 = e.args == (0, 'Error')
144 IS_ABOVE_OPENSSL10 = ssl.OPENSSL_VERSION_INFO >= (1, 1)
145 del ssl
146 if is_error0 and IS_ABOVE_OPENSSL10:
147 return None, {}
148 raise
149 return wrapper
150 patch_builtin_ssl_wrap(v, accept_socket_error_0)
151
152
153 def patch_request_unique_id(v):
154 """
155 Older versions of cherrypy don't include request.unique_id field (a lazily
156 calculated UUID4).
157
158 Monkey-patching is preferred over alternatives as inheritance, as it'd break
159 type checks (cherrypy/lib/cgtools.py: `isinstance(obj, _cprequest.Request)`)
160 """
161 if v < StrictVersion('11.1.0'):
162 import uuid
163 from functools import update_wrapper
164 from cherrypy._cprequest import Request
165
166 class LazyUUID4(object):
167 def __str__(self):
168 """Return UUID4 and keep it for future calls."""
169 return str(self.uuid4)
170
171 @property
172 def uuid4(self):
173 """Provide unique id on per-request basis using UUID4.
174 It's evaluated lazily on render.
175 """
176 try:
177 self._uuid4 # type: ignore
178 except AttributeError:
179 # evaluate on first access
180 self._uuid4 = uuid.uuid4()
181
182 return self._uuid4
183
184 old_init = Request.__init__
185
186 def init_with_unique_id(self, *args, **kwargs):
187 old_init(self, *args, **kwargs)
188 self.unique_id = LazyUUID4()
189
190 Request.__init__ = update_wrapper(init_with_unique_id, old_init)
191
192
193 def patch_cherrypy(v):
194 patch_http_connection_init(v)
195 skip_wait_for_occupied_port(v)
196 accept_exceptions_from_builtin_ssl(v)
197 accept_socket_error_0(v)
198 patch_request_unique_id(v)