Fix api exception with unicode tenant name.
There are a lot request debug logging in Trove, when some values of headers are encoded in utf8, UnicodeEncodeError will be raised by webob.Request. Override how webob.Request is represented will fix. Closes-Bug: #1720121 Change-Id: I91683b8dd24262b0f643e8d2bc7886a7c03be40a Signed-off-by: Zhao Chao <zhaochao1984@gmail.com>
This commit is contained in:
		@@ -21,6 +21,7 @@ import webob.exc
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
from trove.common import exception
 | 
					from trove.common import exception
 | 
				
			||||||
from trove.common.i18n import _
 | 
					from trove.common.i18n import _
 | 
				
			||||||
 | 
					from trove.common.utils import req_to_text
 | 
				
			||||||
from trove.common import wsgi
 | 
					from trove.common import wsgi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
LOG = logging.getLogger(__name__)
 | 
					LOG = logging.getLogger(__name__)
 | 
				
			||||||
@@ -64,7 +65,8 @@ class TenantBasedAuth(object):
 | 
				
			|||||||
            LOG.debug(strutils.mask_password(
 | 
					            LOG.debug(strutils.mask_password(
 | 
				
			||||||
                      _("Authorized tenant '%(tenant_id)s' request: "
 | 
					                      _("Authorized tenant '%(tenant_id)s' request: "
 | 
				
			||||||
                        "%(request)s") %
 | 
					                        "%(request)s") %
 | 
				
			||||||
                      {'tenant_id': tenant_id, 'request': request}))
 | 
					                      {'tenant_id': tenant_id,
 | 
				
			||||||
 | 
					                       'request': req_to_text(request)}))
 | 
				
			||||||
            return True
 | 
					            return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        msg = _(
 | 
					        msg = _(
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -41,6 +41,7 @@ from xml.parsers import expat
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
from trove.common import base_exception
 | 
					from trove.common import base_exception
 | 
				
			||||||
from trove.common.i18n import _
 | 
					from trove.common.i18n import _
 | 
				
			||||||
 | 
					from trove.common.utils import req_to_text
 | 
				
			||||||
from trove.common import xmlutils
 | 
					from trove.common import xmlutils
 | 
				
			||||||
 | 
					
 | 
				
			||||||
socket_opts = [
 | 
					socket_opts = [
 | 
				
			||||||
@@ -332,6 +333,8 @@ class Request(webob.Request):
 | 
				
			|||||||
            raise base_exception.InvalidContentType(content_type=content_type)
 | 
					            raise base_exception.InvalidContentType(content_type=content_type)
 | 
				
			||||||
        return content_type
 | 
					        return content_type
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    __str__ = req_to_text
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Resource(object):
 | 
					class Resource(object):
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -26,6 +26,7 @@ import jinja2
 | 
				
			|||||||
from oslo_concurrency import processutils
 | 
					from oslo_concurrency import processutils
 | 
				
			||||||
from oslo_log import log as logging
 | 
					from oslo_log import log as logging
 | 
				
			||||||
from oslo_service import loopingcall
 | 
					from oslo_service import loopingcall
 | 
				
			||||||
 | 
					from oslo_utils.encodeutils import safe_encode
 | 
				
			||||||
from oslo_utils import importutils
 | 
					from oslo_utils import importutils
 | 
				
			||||||
from oslo_utils import strutils
 | 
					from oslo_utils import strutils
 | 
				
			||||||
from passlib import pwd
 | 
					from passlib import pwd
 | 
				
			||||||
@@ -383,3 +384,28 @@ def to_mb(bytes):
 | 
				
			|||||||
    size = bytes / 1024.0 ** 2
 | 
					    size = bytes / 1024.0 ** 2
 | 
				
			||||||
    # Make sure we don't return 0.0 if the size is greater than 0
 | 
					    # Make sure we don't return 0.0 if the size is greater than 0
 | 
				
			||||||
    return max(round(size, 2), 0.01)
 | 
					    return max(round(size, 2), 0.01)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def req_to_text(req):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    We do a lot request logging for debug, but if the value of one
 | 
				
			||||||
 | 
					    requst header is encoded in utf-8, an UnicodeEncodeError will
 | 
				
			||||||
 | 
					    be raised. So we should carefully encode request headers.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    To be consitent with webob, main procedures are copied from
 | 
				
			||||||
 | 
					    webob.Request.as_bytes.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    url = req.url
 | 
				
			||||||
 | 
					    host = req.host_url
 | 
				
			||||||
 | 
					    assert url.startswith(host)
 | 
				
			||||||
 | 
					    url = url[len(host):]
 | 
				
			||||||
 | 
					    parts = [safe_encode('%s %s %s' % (req.method, url, req.http_version))]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for k, v in sorted(req.headers.items()):
 | 
				
			||||||
 | 
					        header = safe_encode('%s: %s' % (k, v))
 | 
				
			||||||
 | 
					        parts.append(header)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if req.body:
 | 
				
			||||||
 | 
					        parts.extend([b'', safe_encode(req.body)])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return b'\r\n'.join(parts).decode(req.charset)
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										35
									
								
								trove/tests/unittests/common/test_auth.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								trove/tests/unittests/common/test_auth.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
				
			|||||||
 | 
					#    Licensed under the Apache License, Version 2.0 (the "License"); you may
 | 
				
			||||||
 | 
					#    not use this file except in compliance with the License. You may obtain
 | 
				
			||||||
 | 
					#    a copy of the License at
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					#         http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					#    Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
				
			||||||
 | 
					#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
				
			||||||
 | 
					#    License for the specific language governing permissions and limitations
 | 
				
			||||||
 | 
					#    under the License.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import webob
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from trove.common import auth
 | 
				
			||||||
 | 
					from trove.tests.unittests import trove_testtools
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TestAuth(trove_testtools.TestCase):
 | 
				
			||||||
 | 
					    def test_unicode_characters_in_headers(self):
 | 
				
			||||||
 | 
					        middleware = auth.AuthorizationMiddleware(
 | 
				
			||||||
 | 
					            "test_trove",
 | 
				
			||||||
 | 
					            [auth.TenantBasedAuth()])
 | 
				
			||||||
 | 
					        tenant_id = 'test_tenant_id'
 | 
				
			||||||
 | 
					        url = '/%s/instances' % tenant_id
 | 
				
			||||||
 | 
					        req = webob.Request.blank(url)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # test string with chinese characters
 | 
				
			||||||
 | 
					        test_str = u'\u6d4b\u8bd5'
 | 
				
			||||||
 | 
					        req.headers = {
 | 
				
			||||||
 | 
					            'X-Tenant-ID': tenant_id,
 | 
				
			||||||
 | 
					            'X-Auth-Project-Id': test_str
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        # invocation
 | 
				
			||||||
 | 
					        middleware.process_request(req)
 | 
				
			||||||
@@ -22,6 +22,7 @@ from trove.common import exception
 | 
				
			|||||||
from trove.common import utils
 | 
					from trove.common import utils
 | 
				
			||||||
from trove.tests.unittests import trove_testtools
 | 
					from trove.tests.unittests import trove_testtools
 | 
				
			||||||
from trove.tests.util import utils as test_utils
 | 
					from trove.tests.util import utils as test_utils
 | 
				
			||||||
 | 
					import webob
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TestUtils(trove_testtools.TestCase):
 | 
					class TestUtils(trove_testtools.TestCase):
 | 
				
			||||||
@@ -173,3 +174,15 @@ class TestUtils(trove_testtools.TestCase):
 | 
				
			|||||||
        assert_retry(te.test_foo_2, TestEx3, 1, TestEx3)
 | 
					        assert_retry(te.test_foo_2, TestEx3, 1, TestEx3)
 | 
				
			||||||
        assert_retry(te.test_foo_2, TestEx2, 3, TestEx2)
 | 
					        assert_retry(te.test_foo_2, TestEx2, 3, TestEx2)
 | 
				
			||||||
        assert_retry(te.test_foo_2, [TestEx1, TestEx3, TestEx2], 2, TestEx3)
 | 
					        assert_retry(te.test_foo_2, [TestEx1, TestEx3, TestEx2], 2, TestEx3)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_req_to_text(self):
 | 
				
			||||||
 | 
					        req = webob.Request.blank('/')
 | 
				
			||||||
 | 
					        expected = u'GET / HTTP/1.0\r\nHost: localhost:80'
 | 
				
			||||||
 | 
					        self.assertEqual(expected, utils.req_to_text(req))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # add a header containing unicode characters
 | 
				
			||||||
 | 
					        req.headers.update({
 | 
				
			||||||
 | 
					            'X-Auth-Project-Id': u'\u6d4b\u8bd5'})
 | 
				
			||||||
 | 
					        expected = (u'GET / HTTP/1.0\r\nHost: localhost:80\r\n'
 | 
				
			||||||
 | 
					                    u'X-Auth-Project-Id: \u6d4b\u8bd5')
 | 
				
			||||||
 | 
					        self.assertEqual(expected, utils.req_to_text(req))
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user