[openstack-glance/f21] Fix unrestricted traversal flow (RHBZ #1174474)

Haïkel Guémar hguemar at fedoraproject.org
Tue Jan 13 14:08:34 UTC 2015


commit 70a35752f9840959069716f620dfb8bfe5069805
Author: Haikel Guemar <hguemar at fedoraproject.org>
Date:   Tue Jan 13 14:57:25 2015 +0100

    Fix unrestricted traversal flow (RHBZ #1174474)
    
    Make rbd store's pool handling more universal

 ...-rbd-store-s-pool-handling-more-universal.patch |  156 +++++
 ...client-use-v2-patch-api-to-handle-file-an.patch |  668 +++++++++++++++++++
 ...revent-client-use-v2-patch-api-to-handle-.patch |  637 +++++++++++++++++++
 ...client-use-v2-patch-api-to-handle-file-an.patch |  670 ++++++++++++++++++++
 ...-file-swift-config-and-filesystem-schemes.patch |  133 ++++
 openstack-glance.spec                              |   78 ++-
 6 files changed, 2310 insertions(+), 32 deletions(-)
---
diff --git a/0005-Make-rbd-store-s-pool-handling-more-universal.patch b/0005-Make-rbd-store-s-pool-handling-more-universal.patch
new file mode 100644
index 0000000..6c49551
--- /dev/null
+++ b/0005-Make-rbd-store-s-pool-handling-more-universal.patch
@@ -0,0 +1,156 @@
+From ec4a8ccef8340939867a14872d98539ae653587e Mon Sep 17 00:00:00 2001
+From: Flavio Percoco <flavio at redhat.com>
+Date: Tue, 23 Sep 2014 17:55:13 +0200
+Subject: [PATCH] Make rbd store's pool handling more universal
+
+Currently we ignore the pool part of the location throughout the rbd store code.
+If there is a pool specified, use that. Otherwise we can still fall back to the
+configured pool.
+
+This is a required change if we want to support ephemeral disk snapshotting later on
+as in that scenario the ephemeral pool might be a different pool from images pool,
+yet we're going to need to reference the snapshot of a disk in the ephemeral pool.
+
+Resolves rhbz: 1148199
+Upstream-Closes-Bug: 1368128
+Upstream-Icehouse: https://review.openstack.org/#/c/125341/
+Upstream-Juno: https://review.openstack.org/#/c/125341/ (glance_store)
+
+Change-Id: Ie415667a809975948c8cfb71ec63a0905995fa67
+(cherry picked from commit 312e93eb165d66821a9f24c66a507e63e5fda9ab)
+Reviewed-on: https://code.engineering.redhat.com/gerrit/35437
+Reviewed-by: Jon Bernard <jobernar at redhat.com>
+Reviewed-by: Flavio Percoco <fpercoco at redhat.com>
+Tested-by: Flavio Percoco <fpercoco at redhat.com>
+---
+ glance/store/rbd.py                 | 23 +++++++++++++++--------
+ glance/tests/unit/test_rbd_store.py | 12 +++++++-----
+ 2 files changed, 22 insertions(+), 13 deletions(-)
+
+diff --git a/glance/store/rbd.py b/glance/store/rbd.py
+index 75f0270..2974a71 100644
+--- a/glance/store/rbd.py
++++ b/glance/store/rbd.py
+@@ -144,9 +144,10 @@ class ImageIterator(object):
+     Reads data from an RBD image, one chunk at a time.
+     """
+ 
+-    def __init__(self, name, store):
++    def __init__(self, pool, name, snapshot, store):
++        self.pool = pool or store.pool
+         self.name = name
+-        self.pool = store.pool
++        self.snapshot = snapshot
+         self.user = store.user
+         self.conf_file = store.conf_file
+         self.chunk_size = store.chunk_size
+@@ -156,7 +157,8 @@ class ImageIterator(object):
+             with rados.Rados(conffile=self.conf_file,
+                              rados_id=self.user) as conn:
+                 with conn.open_ioctx(self.pool) as ioctx:
+-                    with rbd.Image(ioctx, self.name) as image:
++                    with rbd.Image(ioctx, self.name,
++                                   snapshot=self.snapshot) as image:
+                         img_info = image.stat()
+                         size = img_info['size']
+                         bytes_left = size
+@@ -211,7 +213,8 @@ class Store(glance.store.base.Store):
+         :raises `glance.exception.NotFound` if image does not exist
+         """
+         loc = location.store_location
+-        return (ImageIterator(loc.image, self), self.get_size(location))
++        return (ImageIterator(loc.pool, loc.image, loc.snapshot, self),
++                self.get_size(location))
+ 
+     def get_size(self, location):
+         """
+@@ -223,9 +226,12 @@ class Store(glance.store.base.Store):
+         :raises `glance.exception.NotFound` if image does not exist
+         """
+         loc = location.store_location
++        # if there is a pool specific in the location, use it; otherwise
++        # we fall back to the default pool specified in the config
++        target_pool = loc.pool or self.pool
+         with rados.Rados(conffile=self.conf_file,
+                          rados_id=self.user) as conn:
+-            with conn.open_ioctx(self.pool) as ioctx:
++            with conn.open_ioctx(target_pool) as ioctx:
+                 try:
+                     with rbd.Image(ioctx, loc.image,
+                                    snapshot=loc.snapshot) as image:
+@@ -260,7 +266,7 @@ class Store(glance.store.base.Store):
+             librbd.create(ioctx, image_name, size, order, old_format=True)
+             return StoreLocation({'image': image_name})
+ 
+-    def _delete_image(self, image_name, snapshot_name=None):
++    def _delete_image(self, target_pool, image_name, snapshot_name=None):
+         """
+         Delete RBD image and snapshot.
+ 
+@@ -271,7 +277,7 @@ class Store(glance.store.base.Store):
+                 InUseByStore if image is in use or snapshot unprotect failed
+         """
+         with rados.Rados(conffile=self.conf_file, rados_id=self.user) as conn:
+-            with conn.open_ioctx(self.pool) as ioctx:
++            with conn.open_ioctx(target_pool) as ioctx:
+                 try:
+                     # First remove snapshot.
+                     if snapshot_name is not None:
+@@ -387,4 +393,5 @@ class Store(glance.store.base.Store):
+                 InUseByStore if image is in use or snapshot unprotect failed
+         """
+         loc = location.store_location
+-        self._delete_image(loc.image, loc.snapshot)
++        target_pool = loc.pool or self.pool
++        self._delete_image(target_pool, loc.image, loc.snapshot)
+diff --git a/glance/tests/unit/test_rbd_store.py b/glance/tests/unit/test_rbd_store.py
+index f21cba5..828fee9 100644
+--- a/glance/tests/unit/test_rbd_store.py
++++ b/glance/tests/unit/test_rbd_store.py
+@@ -37,7 +37,8 @@ class TestStore(base.StoreClearingUnitTest):
+         self.store.chunk_size = 2
+         self.called_commands_actual = []
+         self.called_commands_expected = []
+-        self.store_specs = {'image': 'fake_image',
++        self.store_specs = {'pool': 'fake_pool',
++                            'image': 'fake_image',
+                             'snapshot': 'fake_snapshot'}
+         self.location = StoreLocation(self.store_specs)
+         # Provide enough data to get more than one chunk iteration.
+@@ -99,7 +100,7 @@ class TestStore(base.StoreClearingUnitTest):
+             self.called_commands_actual.append('remove')
+ 
+         self.stubs.Set(mock_rbd.RBD, 'remove', _fake_remove)
+-        self.store._delete_image(self.location)
++        self.store._delete_image('fake_pool', self.location)
+         self.called_commands_expected = ['remove']
+ 
+     def test__delete_image_w_snap(self):
+@@ -115,7 +116,8 @@ class TestStore(base.StoreClearingUnitTest):
+         self.stubs.Set(mock_rbd.RBD, 'remove', _fake_remove)
+         self.stubs.Set(mock_rbd.Image, 'unprotect_snap', _fake_unprotect_snap)
+         self.stubs.Set(mock_rbd.Image, 'remove_snap', _fake_remove_snap)
+-        self.store._delete_image(self.location, snapshot_name='snap')
++        self.store._delete_image('fake_pool', self.location,
++                                 snapshot_name='snap')
+ 
+         self.called_commands_expected = ['unprotect_snap', 'remove_snap',
+                                          'remove']
+@@ -127,7 +129,7 @@ class TestStore(base.StoreClearingUnitTest):
+ 
+         self.stubs.Set(mock_rbd.Image, 'unprotect_snap', _fake_unprotect_snap)
+         self.assertRaises(exception.NotFound, self.store._delete_image,
+-                          self.location, snapshot_name='snap')
++                          'fake_pool', self.location, snapshot_name='snap')
+ 
+         self.called_commands_expected = ['unprotect_snap']
+ 
+@@ -138,7 +140,7 @@ class TestStore(base.StoreClearingUnitTest):
+ 
+         self.stubs.Set(mock_rbd.RBD, 'remove', _fake_remove)
+         self.assertRaises(exception.NotFound, self.store._delete_image,
+-                          self.location, snapshot_name='snap')
++                          'fake_pool', self.location, snapshot_name='snap')
+ 
+         self.called_commands_expected = ['remove']
+ 
diff --git a/0006-To-prevent-client-use-v2-patch-api-to-handle-file-an.patch b/0006-To-prevent-client-use-v2-patch-api-to-handle-file-an.patch
new file mode 100644
index 0000000..1ee6607
--- /dev/null
+++ b/0006-To-prevent-client-use-v2-patch-api-to-handle-file-an.patch
@@ -0,0 +1,668 @@
+From 4c60b6f023e0f6cd1f59760e046885dad71c8518 Mon Sep 17 00:00:00 2001
+From: Zhi Yan Liu <zhiyanl at cn.ibm.com>
+Date: Mon, 15 Dec 2014 12:29:55 +0800
+Subject: [PATCH] To prevent client use v2 patch api to handle file and swift
+ location
+
+The change will be used to restrict client to download and delete any
+file in glance-api server. The same resone and logic as what we did in
+v1:
+https://github.com/openstack/glance/blob/master/glance/api/v1/images.py#L429
+
+Closes-Bug: bug/1400966
+DocImpact
+
+Note: Even this change could fully resolve the problem for Glance, but
+we still need to fix this issue from glance_store perspective
+separatelly due to other projects can use the lib directly.
+
+Conflicts:
+	glance/api/v1/images.py
+	glance/common/store_utils.py
+	glance/location.py
+	glance/tests/functional/v1/test_copy_to_file.py
+	glance/tests/functional/v2/test_images.py
+	glance/tests/unit/test_store_image.py
+	glance/tests/unit/test_store_location.py
+	glance/tests/unit/utils.py
+	glance/tests/unit/v1/test_api.py
+
+(cherry picked from commit 4afdb017aa1ccef01482f117cb8d0736a6da38ed)
+Signed-off-by: Zhi Yan Liu <zhiyanl at cn.ibm.com>
+
+Change-Id: Id04c05b997ff93f302bafc7af8ebb27a54a84f91
+Resolves: rhbz #1174483
+Resolves: rhbz #1174484
+Upstream-Closes-Bug: #1400966
+Upstream-Kilo: https://review.openstack.org/#/c/141706/
+Upstream-Juno: https://review.openstack.org/#/c/142373/
+Upstream-Icehouse: https://review.openstack.org/#/c/142703/
+Upstream-change-Id: I72dbead3cb2dcb87f52658ddb880e26880cc229b
+Reviewed-on: https://code.engineering.redhat.com/gerrit/38836
+Reviewed-by: Eoghan Glynn <eglynn at redhat.com>
+Tested-by: Eoghan Glynn <eglynn at redhat.com>
+---
+ glance/api/v1/images.py                         |  27 ++--
+ glance/store/__init__.py                        |  30 ++++-
+ glance/tests/functional/v1/test_copy_to_file.py |  30 ++++-
+ glance/tests/functional/v2/test_images.py       | 169 +++++++++++-------------
+ glance/tests/unit/test_store_image.py           |   3 +-
+ glance/tests/unit/test_store_location.py        |  30 ++++-
+ glance/tests/unit/utils.py                      |   9 +-
+ glance/tests/unit/v1/test_api.py                |  77 ++++++++++-
+ 8 files changed, 252 insertions(+), 123 deletions(-)
+
+diff --git a/glance/api/v1/images.py b/glance/api/v1/images.py
+index dd6ec06..2f8afa0 100644
+--- a/glance/api/v1/images.py
++++ b/glance/api/v1/images.py
+@@ -48,6 +48,7 @@ from glance.store import get_known_schemes
+ from glance.store import get_size_from_backend
+ from glance.store import get_store_from_location
+ from glance.store import get_store_from_scheme
++from glance.store import validate_external_location
+ 
+ LOG = logging.getLogger(__name__)
+ SUPPORTED_PARAMS = glance.api.v1.SUPPORTED_PARAMS
+@@ -404,23 +405,19 @@ class Controller(controller.BaseController):
+     @staticmethod
+     def _validate_source(source, req):
+         """
+-        External sources (as specified via the location or copy-from headers)
+-        are supported only over non-local store types, i.e. S3, Swift, HTTP.
+-        Note the absence of file:// for security reasons, see LP bug #942118.
+-        If the above constraint is violated, we reject with 400 "Bad Request".
++        To validate if external sources (as specified via the location
++        or copy-from headers) are supported. Otherwise we reject
++        with 400 "Bad Request".
+         """
+         if source:
+-            pieces = urlparse.urlparse(source)
+-            schemes = [scheme for scheme in get_known_schemes()
+-                       if scheme != 'file']
+-            for scheme in schemes:
+-                if pieces.scheme == scheme:
+-                    return source
+-            msg = _("External sourcing not supported for store %s") % source
+-            LOG.debug(msg)
+-            raise HTTPBadRequest(explanation=msg,
+-                                 request=req,
+-                                 content_type="text/plain")
++            if validate_external_location(source):
++                return source
++            else:
++                msg = _("External source are not supported: '%s'") % source
++                LOG.debug(msg)
++                raise HTTPBadRequest(explanation=msg,
++                                     request=req,
++                                     content_type="text/plain")
+ 
+     @staticmethod
+     def _copy_from(req):
+diff --git a/glance/store/__init__.py b/glance/store/__init__.py
+index c73d3da..2ae8caa 100644
+--- a/glance/store/__init__.py
++++ b/glance/store/__init__.py
+@@ -19,6 +19,7 @@ import sys
+ 
+ from oslo.config import cfg
+ import six
++import six.moves.urllib.parse as urlparse
+ 
+ from glance.common import exception
+ from glance.common import utils
+@@ -419,6 +420,24 @@ def set_acls(context, location_uri, public=False, read_tenants=[],
+         LOG.debug(_("Skipping store.set_acls... not implemented."))
+ 
+ 
++def validate_external_location(uri):
++    """
++    Validate if URI of external location are supported.
++
++    Only over non-local store types are OK, i.e. S3, Swift,
++    HTTP. Note the absence of 'file://' for security reasons,
++    see LP bug #942118, 1400966, 'swift+config://' is also
++    absent for security reasons, see LP bug #1334196.
++
++    :param uri: The URI of external image location.
++    :return: Whether given URI of external image location are OK.
++    """
++    pieces = urlparse.urlparse(uri)
++    valid_schemes = [scheme for scheme in get_known_schemes()
++                     if scheme != 'file' and scheme != 'swift+config']
++    return pieces.scheme in valid_schemes
++
++
+ class ImageRepoProxy(glance.domain.proxy.Repo):
+ 
+     def __init__(self, image_repo, context, store_api):
+@@ -451,22 +470,23 @@ class ImageRepoProxy(glance.domain.proxy.Repo):
+ 
+ 
+ def _check_location_uri(context, store_api, uri):
+-    """
+-    Check if an image location uri is valid.
++    """Check if an image location is valid.
+ 
+     :param context: Glance request context
+     :param store_api: store API module
+     :param uri: location's uri string
+     """
++
+     is_ok = True
+     try:
+-        size = store_api.get_size_from_backend(context, uri)
+         # NOTE(zhiyan): Some stores return zero when it catch exception
+-        is_ok = size > 0
++        is_ok = (store_api.validate_external_location(uri) and
++                 store_api.get_size_from_backend(uri, context=context) > 0)
+     except (exception.UnknownScheme, exception.NotFound):
+         is_ok = False
+     if not is_ok:
+-        raise exception.BadStoreUri(_('Invalid location: %s') % uri)
++        reason = _('Invalid location')
++        raise exception.BadStoreUri(message=reason)
+ 
+ 
+ def _check_image_location(context, store_api, location):
+diff --git a/glance/tests/functional/v1/test_copy_to_file.py b/glance/tests/functional/v1/test_copy_to_file.py
+index ae2c320..2c5d833 100644
+--- a/glance/tests/functional/v1/test_copy_to_file.py
++++ b/glance/tests/functional/v1/test_copy_to_file.py
+@@ -248,9 +248,35 @@ class TestCopyToFile(functional.FunctionalTest):
+         path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
+         http = httplib2.Http()
+         response, content = http.request(path, 'POST', headers=headers)
+-        self.assertEqual(response.status, 400, content)
++        self.assertEqual(400, response.status, content)
+ 
+-        expected = 'External sourcing not supported for store ' + copy_from
++        expected = 'External source are not supported: \'%s\'' % copy_from
++        msg = 'expected "%s" in "%s"' % (expected, content)
++        self.assertTrue(expected in content, msg)
++
++        self.stop_servers()
++
++    @skip_if_disabled
++    def test_copy_from_swift_config(self):
++        """
++        Ensure we can't copy from swift+config
++        """
++        self.cleanup()
++
++        self.start_servers(**self.__dict__.copy())
++
++        # POST /images with public image copied from file (to file)
++        headers = {'X-Image-Meta-Name': 'copied',
++                   'X-Image-Meta-disk_format': 'raw',
++                   'X-Image-Meta-container_format': 'ovf',
++                   'X-Image-Meta-Is-Public': 'True',
++                   'X-Glance-API-Copy-From': 'swift+config://xxx'}
++        path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
++        http = httplib2.Http()
++        response, content = http.request(path, 'POST', headers=headers)
++        self.assertEqual(400, response.status, content)
++
++        expected = 'External source are not supported: \'swift+config://xxx\''
+         msg = 'expected "%s" in "%s"' % (expected, content)
+         self.assertTrue(expected in content, msg)
+ 
+diff --git a/glance/tests/functional/v2/test_images.py b/glance/tests/functional/v2/test_images.py
+index 4247434..1bb0e56 100644
+--- a/glance/tests/functional/v2/test_images.py
++++ b/glance/tests/functional/v2/test_images.py
+@@ -15,7 +15,6 @@
+ 
+ import os
+ import signal
+-import tempfile
+ import uuid
+ 
+ import requests
+@@ -38,6 +37,19 @@ class TestImages(functional.FunctionalTest):
+         self.cleanup()
+         self.api_server.deployment_flavor = 'noauth'
+         self.start_servers(**self.__dict__.copy())
++        for i in range(3):
++            ret = test_http.http_server("foo_image_id%d" % i,
++                                        "foo_image%d" % i)
++            setattr(self, 'http_server%d_pid' % i, ret[0])
++            setattr(self, 'http_port%d' % i, ret[1])
++
++    def tearDown(self):
++        for i in range(3):
++            pid = getattr(self, 'http_server%d_pid' % i, None)
++            if pid:
++                os.kill(pid, signal.SIGKILL)
++
++        super(TestImages, self).tearDown()
+ 
+     def _url(self, path):
+         return 'http://127.0.0.1:%d%s' % (self.api_port, path)
+@@ -282,21 +294,15 @@ class TestImages(functional.FunctionalTest):
+         self.assertEqual(413, response.status_code, response.text)
+ 
+         # Adding 3 image locations should fail since configured limit is 2
+-        for i in range(3):
+-            file_path = os.path.join(self.test_dir, 'fake_image_%i' % i)
+-            with open(file_path, 'w') as fap:
+-                fap.write('glance')
+-
+         path = self._url('/v2/images/%s' % image_id)
+         media_type = 'application/openstack-images-v2.1-json-patch'
+         headers = self._headers({'content-type': media_type})
+         changes = []
+         for i in range(3):
++            url = ('http://127.0.0.1:%s/foo_image' %
++                   getattr(self, 'http_port%d' % i))
+             changes.append({'op': 'add', 'path': '/locations/-',
+-                            'value': {'url': 'file://{0}'.format(
+-                                os.path.join(self.test_dir,
+-                                             'fake_image_%i' % i)),
+-                                      'metadata': {}},
++                            'value': {'url': url, 'metadata': {}},
+                             })
+ 
+         data = jsonutils.dumps(changes)
+@@ -1811,17 +1817,14 @@ class TestImages(functional.FunctionalTest):
+         self.assertNotIn('size', image)
+         self.assertNotIn('virtual_size', image)
+ 
+-        file_path = os.path.join(self.test_dir, 'fake_image')
+-        with open(file_path, 'w') as fap:
+-            fap.write('glance')
+-
+         # Update locations for the queued image
+         path = self._url('/v2/images/%s' % image_id)
+         media_type = 'application/openstack-images-v2.1-json-patch'
+         headers = self._headers({'content-type': media_type})
++        url = 'http://127.0.0.1:%s/foo_image' % self.http_port0
+         data = jsonutils.dumps([{'op': 'replace', 'path': '/locations',
+-                                 'value': [{'url': 'file://' + file_path,
+-                                            'metadata': {}}]}])
++                                 'value': [{'url': url, 'metadata': {}}]
++                                 }])
+         response = requests.patch(path, headers=headers, data=data)
+         self.assertEqual(200, response.status_code, response.text)
+ 
+@@ -1830,7 +1833,51 @@ class TestImages(functional.FunctionalTest):
+         response = requests.get(path, headers=headers)
+         self.assertEqual(200, response.status_code)
+         image = jsonutils.loads(response.text)
+-        self.assertEqual(image['size'], 6)
++        self.assertEqual(10, image['size'])
++
++    def test_update_locations_with_restricted_sources(self):
++        self.start_servers(**self.__dict__.copy())
++        # Create an image
++        path = self._url('/v2/images')
++        headers = self._headers({'content-type': 'application/json'})
++        data = jsonutils.dumps({'name': 'image-1', 'disk_format': 'aki',
++                                'container_format': 'aki'})
++        response = requests.post(path, headers=headers, data=data)
++        self.assertEqual(201, response.status_code)
++
++        # Returned image entity should have a generated id and status
++        image = jsonutils.loads(response.text)
++        image_id = image['id']
++        self.assertEqual('queued', image['status'])
++        self.assertIsNone(image['size'])
++        self.assertIsNone(image['virtual_size'])
++
++        # Update locations for the queued image
++        path = self._url('/v2/images/%s' % image_id)
++        media_type = 'application/openstack-images-v2.1-json-patch'
++        headers = self._headers({'content-type': media_type})
++        data = jsonutils.dumps([{'op': 'replace', 'path': '/locations',
++                                 'value': [{'url': 'file:///foo_image',
++                                            'metadata': {}}]
++                                 }])
++        response = requests.patch(path, headers=headers, data=data)
++        self.assertEqual(400, response.status_code, response.text)
++
++        data = jsonutils.dumps([{'op': 'replace', 'path': '/locations',
++                                 'value': [{'url': 'swift+config:///foo_image',
++                                            'metadata': {}}]
++                                 }])
++        response = requests.patch(path, headers=headers, data=data)
++        self.assertEqual(400, response.status_code, response.text)
++
++
++class TestImagesWithRegistry(TestImages):
++    def setUp(self):
++        super(TestImagesWithRegistry, self).setUp()
++        self.api_server.data_api = (
++            'glance.tests.functional.v2.registry_data_api')
++        self.registry_server.deployment_flavor = 'trusted-auth'
++>>>>>>> 4afdb01... To prevent client use v2 patch api to handle file and swift location
+ 
+ 
+ class TestImageDirectURLVisibility(functional.FunctionalTest):
+@@ -2040,16 +2087,17 @@ class TestImageLocationSelectionStrategy(functional.FunctionalTest):
+         super(TestImageLocationSelectionStrategy, self).setUp()
+         self.cleanup()
+         self.api_server.deployment_flavor = 'noauth'
+-        self.foo_image_file = tempfile.NamedTemporaryFile()
+-        self.foo_image_file.write("foo image file")
+-        self.foo_image_file.flush()
+-        self.addCleanup(self.foo_image_file.close)
+-        ret = test_http.http_server("foo_image_id", "foo_image")
+-        self.http_server_pid, self.http_port = ret
++        for i in range(3):
++            ret = test_http.http_server("foo_image_id%d" % i,
++                                        "foo_image%d" % i)
++            setattr(self, 'http_server%d_pid' % i, ret[0])
++            setattr(self, 'http_port%d' % i, ret[1])
+ 
+     def tearDown(self):
+-        if self.http_server_pid is not None:
+-            os.kill(self.http_server_pid, signal.SIGKILL)
++        for i in range(3):
++            pid = getattr(self, 'http_server%d_pid' % i, None)
++            if pid:
++                os.kill(pid, signal.SIGKILL)
+ 
+         super(TestImageLocationSelectionStrategy, self).tearDown()
+ 
+@@ -2098,73 +2146,14 @@ class TestImageLocationSelectionStrategy(functional.FunctionalTest):
+         self.assertTrue('locations' in image)
+         self.assertTrue(image["locations"] == [])
+ 
+-       # Update image locations via PATCH
+-        path = self._url('/v2/images/%s' % image_id)
+-        media_type = 'application/openstack-images-v2.1-json-patch'
+-        headers = self._headers({'content-type': media_type})
+-        values = [{'url': 'file://%s' % self.foo_image_file.name,
+-                   'metadata': {'idx': '1'}},
+-                  {'url': 'http://127.0.0.1:%s/foo_image' % self.http_port,
+-                   'metadata': {'idx': '0'}}]
+-        doc = [{'op': 'replace',
+-                'path': '/locations',
+-                'value': values}]
+-        data = jsonutils.dumps(doc)
+-        response = requests.patch(path, headers=headers, data=data)
+-        self.assertEqual(200, response.status_code)
+-
+-        # Image locations should be visible
+-        path = self._url('/v2/images/%s' % image_id)
+-        headers = self._headers({'Content-Type': 'application/json'})
+-        response = requests.get(path, headers=headers)
+-        self.assertEqual(200, response.status_code)
+-        image = jsonutils.loads(response.text)
+-        self.assertTrue('locations' in image)
+-        self.assertEqual(image['locations'], values)
+-        self.assertTrue('direct_url' in image)
+-        self.assertEqual(image['direct_url'], values[0]['url'])
+-
+-        self.stop_servers()
+-
+-    def test_image_locatons_with_store_type_strategy(self):
+-        self.api_server.show_image_direct_url = True
+-        self.api_server.show_multiple_locations = True
+-        self.image_location_quota = 10
+-        self.api_server.location_strategy = 'store_type'
+-        preference = "http, swift, filesystem"
+-        self.api_server.store_type_location_strategy_preference = preference
+-        self.start_servers(**self.__dict__.copy())
+-
+-        # Create an image
+-        path = self._url('/v2/images')
+-        headers = self._headers({'content-type': 'application/json'})
+-        data = jsonutils.dumps({'name': 'image-1', 'type': 'kernel',
+-                                'foo': 'bar', 'disk_format': 'aki',
+-                                'container_format': 'aki'})
+-        response = requests.post(path, headers=headers, data=data)
+-        self.assertEqual(201, response.status_code)
+-
+-        # Get the image id
+-        image = jsonutils.loads(response.text)
+-        image_id = image['id']
+-
+-        # Image locations should not be visible before location is set
+-        path = self._url('/v2/images/%s' % image_id)
+-        headers = self._headers({'Content-Type': 'application/json'})
+-        response = requests.get(path, headers=headers)
+-        self.assertEqual(200, response.status_code)
+-        image = jsonutils.loads(response.text)
+-        self.assertTrue('locations' in image)
+-        self.assertTrue(image["locations"] == [])
+-
+-       # Update image locations via PATCH
++        # Update image locations via PATCH
+         path = self._url('/v2/images/%s' % image_id)
+         media_type = 'application/openstack-images-v2.1-json-patch'
+         headers = self._headers({'content-type': media_type})
+-        values = [{'url': 'file://%s' % self.foo_image_file.name,
+-                   'metadata': {'idx': '1'}},
+-                  {'url': 'http://127.0.0.1:%s/foo_image' % self.http_port,
+-                   'metadata': {'idx': '0'}}]
++        values = [{'url': 'http://127.0.0.1:%s/foo_image' % self.http_port0,
++                   'metadata': {}},
++                  {'url': 'http://127.0.0.1:%s/foo_image' % self.http_port1,
++                   'metadata': {}}]
+         doc = [{'op': 'replace',
+                 'path': '/locations',
+                 'value': values}]
+@@ -2172,8 +2161,6 @@ class TestImageLocationSelectionStrategy(functional.FunctionalTest):
+         response = requests.patch(path, headers=headers, data=data)
+         self.assertEqual(200, response.status_code)
+ 
+-        values.sort(key=lambda loc: int(loc['metadata']['idx']))
+-
+         # Image locations should be visible
+         path = self._url('/v2/images/%s' % image_id)
+         headers = self._headers({'Content-Type': 'application/json'})
+diff --git a/glance/tests/unit/test_store_image.py b/glance/tests/unit/test_store_image.py
+index 424915b..9d6cc4e 100644
+--- a/glance/tests/unit/test_store_image.py
++++ b/glance/tests/unit/test_store_image.py
+@@ -16,6 +16,7 @@ import mox
+ 
+ from glance.common import exception
+ import glance.store
++from glance.tests.unit import base as unit_test_base
+ from glance.tests.unit import utils as unit_test_utils
+ from glance.tests import utils
+ 
+@@ -731,7 +732,7 @@ class TestStoreImageRepo(utils.BaseTestCase):
+         self.assertEqual(acls['read'], [TENANT2])
+ 
+ 
+-class TestImageFactory(utils.BaseTestCase):
++class TestImageFactory(unit_test_base.StoreClearingUnitTest):
+ 
+     def setUp(self):
+         super(TestImageFactory, self).setUp()
+diff --git a/glance/tests/unit/test_store_location.py b/glance/tests/unit/test_store_location.py
+index df8d5d7..a19a33a 100644
+--- a/glance/tests/unit/test_store_location.py
++++ b/glance/tests/unit/test_store_location.py
+@@ -488,11 +488,12 @@ class TestStoreLocation(base.StoreClearingUnitTest):
+                               ctx,
+                               store)
+ 
++    class FakeImageProxy():
++        size = None
++        context = None
++        store_api = mock.Mock()
++
+     def test_add_location_for_image_without_size(self):
+-        class FakeImageProxy():
+-            size = None
+-            context = None
+-            store_api = mock.Mock()
+ 
+         def fake_get_size_from_backend(context, uri):
+             return 1
+@@ -504,14 +505,31 @@ class TestStoreLocation(base.StoreClearingUnitTest):
+             loc2 = {'url': 'file:///fake2.img.tar.gz', 'metadata': {}}
+ 
+             # Test for insert location
+-            image1 = FakeImageProxy()
++            image1 = TestStoreLocation.FakeImageProxy()
+             locations = glance.store.StoreLocations(image1, [])
+             locations.insert(0, loc2)
+             self.assertEqual(image1.size, 1)
+ 
+             # Test for set_attr of _locations_proxy
+-            image2 = FakeImageProxy()
++            image2 = TestStoreLocation.FakeImageProxy()
+             locations = glance.store.StoreLocations(image2, [loc1])
+             locations[0] = loc2
+             self.assertTrue(loc2 in locations)
+             self.assertEqual(image2.size, 1)
++
++    def test_add_location_with_restricted_sources(self):
++
++        loc1 = {'url': 'file:///fake1.img.tar.gz', 'metadata': {}}
++        loc2 = {'url': 'swift+config:///xxx', 'metadata': {}}
++
++        # Test for insert location
++        image1 = TestStoreLocation.FakeImageProxy()
++        locations = glance.store.StoreLocations(image1, [])
++        self.assertRaises(exception.BadStoreUri, locations.insert, 0, loc1)
++        self.assertNotIn(loc1, locations)
++
++        # Test for set_attr of _locations_proxy
++        image2 = TestStoreLocation.FakeImageProxy()
++        locations = glance.store.StoreLocations(image2, [loc1])
++        self.assertRaises(exception.BadStoreUri, locations.insert, 0, loc2)
++        self.assertNotIn(loc2, locations)
+diff --git a/glance/tests/unit/utils.py b/glance/tests/unit/utils.py
+index 1c4e16a..e0dfc43 100644
+--- a/glance/tests/unit/utils.py
++++ b/glance/tests/unit/utils.py
+@@ -14,9 +14,9 @@
+ #    under the License.
+ 
+ import urllib
+-import urlparse
+ 
+ from oslo.config import cfg
++import six.moves.urllib.parse as urlparse
+ 
+ from glance.common import exception
+ from glance.common import wsgi
+@@ -113,7 +113,6 @@ class FakeDB(object):
+     def __getattr__(self, key):
+         return getattr(simple_db, key)
+ 
+-
+ class FakeStoreAPI(object):
+     def __init__(self, store_metadata=None):
+         self.data = {
+@@ -188,6 +187,12 @@ class FakeStoreAPI(object):
+     def check_location_metadata(self, val, key=''):
+         glance.store.check_location_metadata(val)
+ 
++    def validate_external_location(self, uri):
++        if uri and urlparse.urlparse(uri).scheme:
++            return glance.store.validate_external_location(uri)
++        else:
++            return True
++
+ 
+ class FakePolicyEnforcer(object):
+     def __init__(self, *_args, **kwargs):
+diff --git a/glance/tests/unit/v1/test_api.py b/glance/tests/unit/v1/test_api.py
+index 5618cb0..9856487 100644
+--- a/glance/tests/unit/v1/test_api.py
++++ b/glance/tests/unit/v1/test_api.py
+@@ -379,7 +379,7 @@ class TestGlanceAPI(base.IsolatedUnitTest):
+ 
+         res = req.get_response(self.api)
+         self.assertEqual(res.status_int, 400)
+-        self.assertTrue('External sourcing not supported' in res.body)
++        self.assertIn('External source are not supported', res.body)
+ 
+     def test_create_with_location_bad_store_uri(self):
+         fixture_headers = {
+@@ -962,6 +962,53 @@ class TestGlanceAPI(base.IsolatedUnitTest):
+             res = req.get_response(self.api)
+             self.assertEqual(res.status_int, 409)
+ 
++    def test_add_location_with_invalid_location_on_conflict_image_size(self):
++        """Tests creates an image from location and conflict image size"""
++        fixture_headers = {'x-image-meta-store': 'file',
++                           'x-image-meta-disk-format': 'vhd',
++                           'x-image-meta-location': 'http://a/b/c.tar.gz',
++                           'x-image-meta-container-format': 'ovf',
++                           'x-image-meta-name': 'fake image #F',
++                           'x-image-meta-size': '1'}
++
++        req = webob.Request.blank("/images")
++        req.headers['Content-Type'] = 'application/octet-stream'
++        req.method = 'POST'
++        for k, v in fixture_headers.iteritems():
++            req.headers[k] = v
++        res = req.get_response(self.api)
++        self.assertEqual(400, res.status_int)
++
++    def test_add_location_with_invalid_location_on_restricted_sources(self):
++        """Tests creates an image from location and restricted sources"""
++        fixture_headers = {'x-image-meta-store': 'file',
++                           'x-image-meta-disk-format': 'vhd',
++                           'x-image-meta-location': 'file:///etc/passwd',
++                           'x-image-meta-container-format': 'ovf',
++                           'x-image-meta-name': 'fake image #F'}
++
++        req = webob.Request.blank("/images")
++        req.headers['Content-Type'] = 'application/octet-stream'
++        req.method = 'POST'
++        for k, v in fixture_headers.iteritems():
++            req.headers[k] = v
++        res = req.get_response(self.api)
++        self.assertEqual(400, res.status_int)
++
++        fixture_headers = {'x-image-meta-store': 'file',
++                           'x-image-meta-disk-format': 'vhd',
++                           'x-image-meta-location': 'swift+config://xxx',
++                           'x-image-meta-container-format': 'ovf',
++                           'x-image-meta-name': 'fake image #F'}
++
++        req = webob.Request.blank("/images")
++        req.headers['Content-Type'] = 'application/octet-stream'
++        req.method = 'POST'
++        for k, v in fixture_headers.iteritems():
++            req.headers[k] = v
++        res = req.get_response(self.api)
++        self.assertEqual(400, res.status_int)
++
+     def test_add_copy_from_with_location(self):
+         """Tests creates an image from copy-from and location"""
+         fixture_headers = {'x-image-meta-store': 'file',
+@@ -978,6 +1025,34 @@ class TestGlanceAPI(base.IsolatedUnitTest):
+         res = req.get_response(self.api)
+         self.assertEqual(res.status_int, 400)
+ 
++    def test_add_copy_from_with_restricted_sources(self):
++        """Tests creates an image from copy-from with restricted sources"""
++        fixture_headers = {'x-image-meta-store': 'file',
++                           'x-image-meta-disk-format': 'vhd',
++                           'x-glance-api-copy-from': 'file:///etc/passwd',
++                           'x-image-meta-container-format': 'ovf',
++                           'x-image-meta-name': 'fake image #F'}
++
++        req = webob.Request.blank("/images")
++        req.method = 'POST'
++        for k, v in six.iteritems(fixture_headers):
++            req.headers[k] = v
++        res = req.get_response(self.api)
++        self.assertEqual(400, res.status_int)
++
++        fixture_headers = {'x-image-meta-store': 'file',
++                           'x-image-meta-disk-format': 'vhd',
++                           'x-glance-api-copy-from': 'swift+config://xxx',
++                           'x-image-meta-container-format': 'ovf',
++                           'x-image-meta-name': 'fake image #F'}
++
++        req = webob.Request.blank("/images")
++        req.method = 'POST'
++        for k, v in six.iteritems(fixture_headers):
++            req.headers[k] = v
++        res = req.get_response(self.api)
++        self.assertEqual(400, res.status_int)
++
+     def test_add_copy_from_upload_image_unauthorized_with_body(self):
+         rules = {"upload_image": '!', "modify_image": '@',
+                  "add_image": '@'}
diff --git a/0007-Revert-To-prevent-client-use-v2-patch-api-to-handle-.patch b/0007-Revert-To-prevent-client-use-v2-patch-api-to-handle-.patch
new file mode 100644
index 0000000..5726722
--- /dev/null
+++ b/0007-Revert-To-prevent-client-use-v2-patch-api-to-handle-.patch
@@ -0,0 +1,637 @@
+From ebdddcd75040467447ec27f6ad28f4dfd757206a Mon Sep 17 00:00:00 2001
+From: Lon Hohberger <lhh at redhat.com>
+Date: Tue, 23 Dec 2014 13:42:45 -0500
+Subject: [PATCH] Revert "To prevent client use v2 patch api to handle file and
+ swift location"
+
+This reverts commit 8b22786cd7d5e50235389548cf5e9f15aa0f06de.
+
+Change-Id: Ide10810280aa0f37cf45e9aeee60caa8829b5003
+Reviewed-on: https://code.engineering.redhat.com/gerrit/39076
+Reviewed-by: Lon Hohberger <lhh at redhat.com>
+Tested-by: Lon Hohberger <lhh at redhat.com>
+---
+ glance/api/v1/images.py                         |  27 ++--
+ glance/store/__init__.py                        |  30 +----
+ glance/tests/functional/v1/test_copy_to_file.py |  30 +----
+ glance/tests/functional/v2/test_images.py       | 169 +++++++++++++-----------
+ glance/tests/unit/test_store_image.py           |   3 +-
+ glance/tests/unit/test_store_location.py        |  30 +----
+ glance/tests/unit/utils.py                      |   9 +-
+ glance/tests/unit/v1/test_api.py                |  77 +----------
+ 8 files changed, 123 insertions(+), 252 deletions(-)
+
+diff --git a/glance/api/v1/images.py b/glance/api/v1/images.py
+index 2f8afa0..dd6ec06 100644
+--- a/glance/api/v1/images.py
++++ b/glance/api/v1/images.py
+@@ -48,7 +48,6 @@ from glance.store import get_known_schemes
+ from glance.store import get_size_from_backend
+ from glance.store import get_store_from_location
+ from glance.store import get_store_from_scheme
+-from glance.store import validate_external_location
+ 
+ LOG = logging.getLogger(__name__)
+ SUPPORTED_PARAMS = glance.api.v1.SUPPORTED_PARAMS
+@@ -405,19 +404,23 @@ class Controller(controller.BaseController):
+     @staticmethod
+     def _validate_source(source, req):
+         """
+-        To validate if external sources (as specified via the location
+-        or copy-from headers) are supported. Otherwise we reject
+-        with 400 "Bad Request".
++        External sources (as specified via the location or copy-from headers)
++        are supported only over non-local store types, i.e. S3, Swift, HTTP.
++        Note the absence of file:// for security reasons, see LP bug #942118.
++        If the above constraint is violated, we reject with 400 "Bad Request".
+         """
+         if source:
+-            if validate_external_location(source):
+-                return source
+-            else:
+-                msg = _("External source are not supported: '%s'") % source
+-                LOG.debug(msg)
+-                raise HTTPBadRequest(explanation=msg,
+-                                     request=req,
+-                                     content_type="text/plain")
++            pieces = urlparse.urlparse(source)
++            schemes = [scheme for scheme in get_known_schemes()
++                       if scheme != 'file']
++            for scheme in schemes:
++                if pieces.scheme == scheme:
++                    return source
++            msg = _("External sourcing not supported for store %s") % source
++            LOG.debug(msg)
++            raise HTTPBadRequest(explanation=msg,
++                                 request=req,
++                                 content_type="text/plain")
+ 
+     @staticmethod
+     def _copy_from(req):
+diff --git a/glance/store/__init__.py b/glance/store/__init__.py
+index 2ae8caa..c73d3da 100644
+--- a/glance/store/__init__.py
++++ b/glance/store/__init__.py
+@@ -19,7 +19,6 @@ import sys
+ 
+ from oslo.config import cfg
+ import six
+-import six.moves.urllib.parse as urlparse
+ 
+ from glance.common import exception
+ from glance.common import utils
+@@ -420,24 +419,6 @@ def set_acls(context, location_uri, public=False, read_tenants=[],
+         LOG.debug(_("Skipping store.set_acls... not implemented."))
+ 
+ 
+-def validate_external_location(uri):
+-    """
+-    Validate if URI of external location are supported.
+-
+-    Only over non-local store types are OK, i.e. S3, Swift,
+-    HTTP. Note the absence of 'file://' for security reasons,
+-    see LP bug #942118, 1400966, 'swift+config://' is also
+-    absent for security reasons, see LP bug #1334196.
+-
+-    :param uri: The URI of external image location.
+-    :return: Whether given URI of external image location are OK.
+-    """
+-    pieces = urlparse.urlparse(uri)
+-    valid_schemes = [scheme for scheme in get_known_schemes()
+-                     if scheme != 'file' and scheme != 'swift+config']
+-    return pieces.scheme in valid_schemes
+-
+-
+ class ImageRepoProxy(glance.domain.proxy.Repo):
+ 
+     def __init__(self, image_repo, context, store_api):
+@@ -470,23 +451,22 @@ class ImageRepoProxy(glance.domain.proxy.Repo):
+ 
+ 
+ def _check_location_uri(context, store_api, uri):
+-    """Check if an image location is valid.
++    """
++    Check if an image location uri is valid.
+ 
+     :param context: Glance request context
+     :param store_api: store API module
+     :param uri: location's uri string
+     """
+-
+     is_ok = True
+     try:
++        size = store_api.get_size_from_backend(context, uri)
+         # NOTE(zhiyan): Some stores return zero when it catch exception
+-        is_ok = (store_api.validate_external_location(uri) and
+-                 store_api.get_size_from_backend(uri, context=context) > 0)
++        is_ok = size > 0
+     except (exception.UnknownScheme, exception.NotFound):
+         is_ok = False
+     if not is_ok:
+-        reason = _('Invalid location')
+-        raise exception.BadStoreUri(message=reason)
++        raise exception.BadStoreUri(_('Invalid location: %s') % uri)
+ 
+ 
+ def _check_image_location(context, store_api, location):
+diff --git a/glance/tests/functional/v1/test_copy_to_file.py b/glance/tests/functional/v1/test_copy_to_file.py
+index 2c5d833..ae2c320 100644
+--- a/glance/tests/functional/v1/test_copy_to_file.py
++++ b/glance/tests/functional/v1/test_copy_to_file.py
+@@ -248,35 +248,9 @@ class TestCopyToFile(functional.FunctionalTest):
+         path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
+         http = httplib2.Http()
+         response, content = http.request(path, 'POST', headers=headers)
+-        self.assertEqual(400, response.status, content)
++        self.assertEqual(response.status, 400, content)
+ 
+-        expected = 'External source are not supported: \'%s\'' % copy_from
+-        msg = 'expected "%s" in "%s"' % (expected, content)
+-        self.assertTrue(expected in content, msg)
+-
+-        self.stop_servers()
+-
+-    @skip_if_disabled
+-    def test_copy_from_swift_config(self):
+-        """
+-        Ensure we can't copy from swift+config
+-        """
+-        self.cleanup()
+-
+-        self.start_servers(**self.__dict__.copy())
+-
+-        # POST /images with public image copied from file (to file)
+-        headers = {'X-Image-Meta-Name': 'copied',
+-                   'X-Image-Meta-disk_format': 'raw',
+-                   'X-Image-Meta-container_format': 'ovf',
+-                   'X-Image-Meta-Is-Public': 'True',
+-                   'X-Glance-API-Copy-From': 'swift+config://xxx'}
+-        path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
+-        http = httplib2.Http()
+-        response, content = http.request(path, 'POST', headers=headers)
+-        self.assertEqual(400, response.status, content)
+-
+-        expected = 'External source are not supported: \'swift+config://xxx\''
++        expected = 'External sourcing not supported for store ' + copy_from
+         msg = 'expected "%s" in "%s"' % (expected, content)
+         self.assertTrue(expected in content, msg)
+ 
+diff --git a/glance/tests/functional/v2/test_images.py b/glance/tests/functional/v2/test_images.py
+index 1bb0e56..4247434 100644
+--- a/glance/tests/functional/v2/test_images.py
++++ b/glance/tests/functional/v2/test_images.py
+@@ -15,6 +15,7 @@
+ 
+ import os
+ import signal
++import tempfile
+ import uuid
+ 
+ import requests
+@@ -37,19 +38,6 @@ class TestImages(functional.FunctionalTest):
+         self.cleanup()
+         self.api_server.deployment_flavor = 'noauth'
+         self.start_servers(**self.__dict__.copy())
+-        for i in range(3):
+-            ret = test_http.http_server("foo_image_id%d" % i,
+-                                        "foo_image%d" % i)
+-            setattr(self, 'http_server%d_pid' % i, ret[0])
+-            setattr(self, 'http_port%d' % i, ret[1])
+-
+-    def tearDown(self):
+-        for i in range(3):
+-            pid = getattr(self, 'http_server%d_pid' % i, None)
+-            if pid:
+-                os.kill(pid, signal.SIGKILL)
+-
+-        super(TestImages, self).tearDown()
+ 
+     def _url(self, path):
+         return 'http://127.0.0.1:%d%s' % (self.api_port, path)
+@@ -294,15 +282,21 @@ class TestImages(functional.FunctionalTest):
+         self.assertEqual(413, response.status_code, response.text)
+ 
+         # Adding 3 image locations should fail since configured limit is 2
++        for i in range(3):
++            file_path = os.path.join(self.test_dir, 'fake_image_%i' % i)
++            with open(file_path, 'w') as fap:
++                fap.write('glance')
++
+         path = self._url('/v2/images/%s' % image_id)
+         media_type = 'application/openstack-images-v2.1-json-patch'
+         headers = self._headers({'content-type': media_type})
+         changes = []
+         for i in range(3):
+-            url = ('http://127.0.0.1:%s/foo_image' %
+-                   getattr(self, 'http_port%d' % i))
+             changes.append({'op': 'add', 'path': '/locations/-',
+-                            'value': {'url': url, 'metadata': {}},
++                            'value': {'url': 'file://{0}'.format(
++                                os.path.join(self.test_dir,
++                                             'fake_image_%i' % i)),
++                                      'metadata': {}},
+                             })
+ 
+         data = jsonutils.dumps(changes)
+@@ -1817,14 +1811,17 @@ class TestImages(functional.FunctionalTest):
+         self.assertNotIn('size', image)
+         self.assertNotIn('virtual_size', image)
+ 
++        file_path = os.path.join(self.test_dir, 'fake_image')
++        with open(file_path, 'w') as fap:
++            fap.write('glance')
++
+         # Update locations for the queued image
+         path = self._url('/v2/images/%s' % image_id)
+         media_type = 'application/openstack-images-v2.1-json-patch'
+         headers = self._headers({'content-type': media_type})
+-        url = 'http://127.0.0.1:%s/foo_image' % self.http_port0
+         data = jsonutils.dumps([{'op': 'replace', 'path': '/locations',
+-                                 'value': [{'url': url, 'metadata': {}}]
+-                                 }])
++                                 'value': [{'url': 'file://' + file_path,
++                                            'metadata': {}}]}])
+         response = requests.patch(path, headers=headers, data=data)
+         self.assertEqual(200, response.status_code, response.text)
+ 
+@@ -1833,51 +1830,7 @@ class TestImages(functional.FunctionalTest):
+         response = requests.get(path, headers=headers)
+         self.assertEqual(200, response.status_code)
+         image = jsonutils.loads(response.text)
+-        self.assertEqual(10, image['size'])
+-
+-    def test_update_locations_with_restricted_sources(self):
+-        self.start_servers(**self.__dict__.copy())
+-        # Create an image
+-        path = self._url('/v2/images')
+-        headers = self._headers({'content-type': 'application/json'})
+-        data = jsonutils.dumps({'name': 'image-1', 'disk_format': 'aki',
+-                                'container_format': 'aki'})
+-        response = requests.post(path, headers=headers, data=data)
+-        self.assertEqual(201, response.status_code)
+-
+-        # Returned image entity should have a generated id and status
+-        image = jsonutils.loads(response.text)
+-        image_id = image['id']
+-        self.assertEqual('queued', image['status'])
+-        self.assertIsNone(image['size'])
+-        self.assertIsNone(image['virtual_size'])
+-
+-        # Update locations for the queued image
+-        path = self._url('/v2/images/%s' % image_id)
+-        media_type = 'application/openstack-images-v2.1-json-patch'
+-        headers = self._headers({'content-type': media_type})
+-        data = jsonutils.dumps([{'op': 'replace', 'path': '/locations',
+-                                 'value': [{'url': 'file:///foo_image',
+-                                            'metadata': {}}]
+-                                 }])
+-        response = requests.patch(path, headers=headers, data=data)
+-        self.assertEqual(400, response.status_code, response.text)
+-
+-        data = jsonutils.dumps([{'op': 'replace', 'path': '/locations',
+-                                 'value': [{'url': 'swift+config:///foo_image',
+-                                            'metadata': {}}]
+-                                 }])
+-        response = requests.patch(path, headers=headers, data=data)
+-        self.assertEqual(400, response.status_code, response.text)
+-
+-
+-class TestImagesWithRegistry(TestImages):
+-    def setUp(self):
+-        super(TestImagesWithRegistry, self).setUp()
+-        self.api_server.data_api = (
+-            'glance.tests.functional.v2.registry_data_api')
+-        self.registry_server.deployment_flavor = 'trusted-auth'
+->>>>>>> 4afdb01... To prevent client use v2 patch api to handle file and swift location
++        self.assertEqual(image['size'], 6)
+ 
+ 
+ class TestImageDirectURLVisibility(functional.FunctionalTest):
+@@ -2087,17 +2040,16 @@ class TestImageLocationSelectionStrategy(functional.FunctionalTest):
+         super(TestImageLocationSelectionStrategy, self).setUp()
+         self.cleanup()
+         self.api_server.deployment_flavor = 'noauth'
+-        for i in range(3):
+-            ret = test_http.http_server("foo_image_id%d" % i,
+-                                        "foo_image%d" % i)
+-            setattr(self, 'http_server%d_pid' % i, ret[0])
+-            setattr(self, 'http_port%d' % i, ret[1])
++        self.foo_image_file = tempfile.NamedTemporaryFile()
++        self.foo_image_file.write("foo image file")
++        self.foo_image_file.flush()
++        self.addCleanup(self.foo_image_file.close)
++        ret = test_http.http_server("foo_image_id", "foo_image")
++        self.http_server_pid, self.http_port = ret
+ 
+     def tearDown(self):
+-        for i in range(3):
+-            pid = getattr(self, 'http_server%d_pid' % i, None)
+-            if pid:
+-                os.kill(pid, signal.SIGKILL)
++        if self.http_server_pid is not None:
++            os.kill(self.http_server_pid, signal.SIGKILL)
+ 
+         super(TestImageLocationSelectionStrategy, self).tearDown()
+ 
+@@ -2146,14 +2098,73 @@ class TestImageLocationSelectionStrategy(functional.FunctionalTest):
+         self.assertTrue('locations' in image)
+         self.assertTrue(image["locations"] == [])
+ 
+-        # Update image locations via PATCH
++       # Update image locations via PATCH
++        path = self._url('/v2/images/%s' % image_id)
++        media_type = 'application/openstack-images-v2.1-json-patch'
++        headers = self._headers({'content-type': media_type})
++        values = [{'url': 'file://%s' % self.foo_image_file.name,
++                   'metadata': {'idx': '1'}},
++                  {'url': 'http://127.0.0.1:%s/foo_image' % self.http_port,
++                   'metadata': {'idx': '0'}}]
++        doc = [{'op': 'replace',
++                'path': '/locations',
++                'value': values}]
++        data = jsonutils.dumps(doc)
++        response = requests.patch(path, headers=headers, data=data)
++        self.assertEqual(200, response.status_code)
++
++        # Image locations should be visible
++        path = self._url('/v2/images/%s' % image_id)
++        headers = self._headers({'Content-Type': 'application/json'})
++        response = requests.get(path, headers=headers)
++        self.assertEqual(200, response.status_code)
++        image = jsonutils.loads(response.text)
++        self.assertTrue('locations' in image)
++        self.assertEqual(image['locations'], values)
++        self.assertTrue('direct_url' in image)
++        self.assertEqual(image['direct_url'], values[0]['url'])
++
++        self.stop_servers()
++
++    def test_image_locatons_with_store_type_strategy(self):
++        self.api_server.show_image_direct_url = True
++        self.api_server.show_multiple_locations = True
++        self.image_location_quota = 10
++        self.api_server.location_strategy = 'store_type'
++        preference = "http, swift, filesystem"
++        self.api_server.store_type_location_strategy_preference = preference
++        self.start_servers(**self.__dict__.copy())
++
++        # Create an image
++        path = self._url('/v2/images')
++        headers = self._headers({'content-type': 'application/json'})
++        data = jsonutils.dumps({'name': 'image-1', 'type': 'kernel',
++                                'foo': 'bar', 'disk_format': 'aki',
++                                'container_format': 'aki'})
++        response = requests.post(path, headers=headers, data=data)
++        self.assertEqual(201, response.status_code)
++
++        # Get the image id
++        image = jsonutils.loads(response.text)
++        image_id = image['id']
++
++        # Image locations should not be visible before location is set
++        path = self._url('/v2/images/%s' % image_id)
++        headers = self._headers({'Content-Type': 'application/json'})
++        response = requests.get(path, headers=headers)
++        self.assertEqual(200, response.status_code)
++        image = jsonutils.loads(response.text)
++        self.assertTrue('locations' in image)
++        self.assertTrue(image["locations"] == [])
++
++       # Update image locations via PATCH
+         path = self._url('/v2/images/%s' % image_id)
+         media_type = 'application/openstack-images-v2.1-json-patch'
+         headers = self._headers({'content-type': media_type})
+-        values = [{'url': 'http://127.0.0.1:%s/foo_image' % self.http_port0,
+-                   'metadata': {}},
+-                  {'url': 'http://127.0.0.1:%s/foo_image' % self.http_port1,
+-                   'metadata': {}}]
++        values = [{'url': 'file://%s' % self.foo_image_file.name,
++                   'metadata': {'idx': '1'}},
++                  {'url': 'http://127.0.0.1:%s/foo_image' % self.http_port,
++                   'metadata': {'idx': '0'}}]
+         doc = [{'op': 'replace',
+                 'path': '/locations',
+                 'value': values}]
+@@ -2161,6 +2172,8 @@ class TestImageLocationSelectionStrategy(functional.FunctionalTest):
+         response = requests.patch(path, headers=headers, data=data)
+         self.assertEqual(200, response.status_code)
+ 
++        values.sort(key=lambda loc: int(loc['metadata']['idx']))
++
+         # Image locations should be visible
+         path = self._url('/v2/images/%s' % image_id)
+         headers = self._headers({'Content-Type': 'application/json'})
+diff --git a/glance/tests/unit/test_store_image.py b/glance/tests/unit/test_store_image.py
+index 9d6cc4e..424915b 100644
+--- a/glance/tests/unit/test_store_image.py
++++ b/glance/tests/unit/test_store_image.py
+@@ -16,7 +16,6 @@ import mox
+ 
+ from glance.common import exception
+ import glance.store
+-from glance.tests.unit import base as unit_test_base
+ from glance.tests.unit import utils as unit_test_utils
+ from glance.tests import utils
+ 
+@@ -732,7 +731,7 @@ class TestStoreImageRepo(utils.BaseTestCase):
+         self.assertEqual(acls['read'], [TENANT2])
+ 
+ 
+-class TestImageFactory(unit_test_base.StoreClearingUnitTest):
++class TestImageFactory(utils.BaseTestCase):
+ 
+     def setUp(self):
+         super(TestImageFactory, self).setUp()
+diff --git a/glance/tests/unit/test_store_location.py b/glance/tests/unit/test_store_location.py
+index a19a33a..df8d5d7 100644
+--- a/glance/tests/unit/test_store_location.py
++++ b/glance/tests/unit/test_store_location.py
+@@ -488,12 +488,11 @@ class TestStoreLocation(base.StoreClearingUnitTest):
+                               ctx,
+                               store)
+ 
+-    class FakeImageProxy():
+-        size = None
+-        context = None
+-        store_api = mock.Mock()
+-
+     def test_add_location_for_image_without_size(self):
++        class FakeImageProxy():
++            size = None
++            context = None
++            store_api = mock.Mock()
+ 
+         def fake_get_size_from_backend(context, uri):
+             return 1
+@@ -505,31 +504,14 @@ class TestStoreLocation(base.StoreClearingUnitTest):
+             loc2 = {'url': 'file:///fake2.img.tar.gz', 'metadata': {}}
+ 
+             # Test for insert location
+-            image1 = TestStoreLocation.FakeImageProxy()
++            image1 = FakeImageProxy()
+             locations = glance.store.StoreLocations(image1, [])
+             locations.insert(0, loc2)
+             self.assertEqual(image1.size, 1)
+ 
+             # Test for set_attr of _locations_proxy
+-            image2 = TestStoreLocation.FakeImageProxy()
++            image2 = FakeImageProxy()
+             locations = glance.store.StoreLocations(image2, [loc1])
+             locations[0] = loc2
+             self.assertTrue(loc2 in locations)
+             self.assertEqual(image2.size, 1)
+-
+-    def test_add_location_with_restricted_sources(self):
+-
+-        loc1 = {'url': 'file:///fake1.img.tar.gz', 'metadata': {}}
+-        loc2 = {'url': 'swift+config:///xxx', 'metadata': {}}
+-
+-        # Test for insert location
+-        image1 = TestStoreLocation.FakeImageProxy()
+-        locations = glance.store.StoreLocations(image1, [])
+-        self.assertRaises(exception.BadStoreUri, locations.insert, 0, loc1)
+-        self.assertNotIn(loc1, locations)
+-
+-        # Test for set_attr of _locations_proxy
+-        image2 = TestStoreLocation.FakeImageProxy()
+-        locations = glance.store.StoreLocations(image2, [loc1])
+-        self.assertRaises(exception.BadStoreUri, locations.insert, 0, loc2)
+-        self.assertNotIn(loc2, locations)
+diff --git a/glance/tests/unit/utils.py b/glance/tests/unit/utils.py
+index e0dfc43..1c4e16a 100644
+--- a/glance/tests/unit/utils.py
++++ b/glance/tests/unit/utils.py
+@@ -14,9 +14,9 @@
+ #    under the License.
+ 
+ import urllib
++import urlparse
+ 
+ from oslo.config import cfg
+-import six.moves.urllib.parse as urlparse
+ 
+ from glance.common import exception
+ from glance.common import wsgi
+@@ -113,6 +113,7 @@ class FakeDB(object):
+     def __getattr__(self, key):
+         return getattr(simple_db, key)
+ 
++
+ class FakeStoreAPI(object):
+     def __init__(self, store_metadata=None):
+         self.data = {
+@@ -187,12 +188,6 @@ class FakeStoreAPI(object):
+     def check_location_metadata(self, val, key=''):
+         glance.store.check_location_metadata(val)
+ 
+-    def validate_external_location(self, uri):
+-        if uri and urlparse.urlparse(uri).scheme:
+-            return glance.store.validate_external_location(uri)
+-        else:
+-            return True
+-
+ 
+ class FakePolicyEnforcer(object):
+     def __init__(self, *_args, **kwargs):
+diff --git a/glance/tests/unit/v1/test_api.py b/glance/tests/unit/v1/test_api.py
+index 9856487..5618cb0 100644
+--- a/glance/tests/unit/v1/test_api.py
++++ b/glance/tests/unit/v1/test_api.py
+@@ -379,7 +379,7 @@ class TestGlanceAPI(base.IsolatedUnitTest):
+ 
+         res = req.get_response(self.api)
+         self.assertEqual(res.status_int, 400)
+-        self.assertIn('External source are not supported', res.body)
++        self.assertTrue('External sourcing not supported' in res.body)
+ 
+     def test_create_with_location_bad_store_uri(self):
+         fixture_headers = {
+@@ -962,53 +962,6 @@ class TestGlanceAPI(base.IsolatedUnitTest):
+             res = req.get_response(self.api)
+             self.assertEqual(res.status_int, 409)
+ 
+-    def test_add_location_with_invalid_location_on_conflict_image_size(self):
+-        """Tests creates an image from location and conflict image size"""
+-        fixture_headers = {'x-image-meta-store': 'file',
+-                           'x-image-meta-disk-format': 'vhd',
+-                           'x-image-meta-location': 'http://a/b/c.tar.gz',
+-                           'x-image-meta-container-format': 'ovf',
+-                           'x-image-meta-name': 'fake image #F',
+-                           'x-image-meta-size': '1'}
+-
+-        req = webob.Request.blank("/images")
+-        req.headers['Content-Type'] = 'application/octet-stream'
+-        req.method = 'POST'
+-        for k, v in fixture_headers.iteritems():
+-            req.headers[k] = v
+-        res = req.get_response(self.api)
+-        self.assertEqual(400, res.status_int)
+-
+-    def test_add_location_with_invalid_location_on_restricted_sources(self):
+-        """Tests creates an image from location and restricted sources"""
+-        fixture_headers = {'x-image-meta-store': 'file',
+-                           'x-image-meta-disk-format': 'vhd',
+-                           'x-image-meta-location': 'file:///etc/passwd',
+-                           'x-image-meta-container-format': 'ovf',
+-                           'x-image-meta-name': 'fake image #F'}
+-
+-        req = webob.Request.blank("/images")
+-        req.headers['Content-Type'] = 'application/octet-stream'
+-        req.method = 'POST'
+-        for k, v in fixture_headers.iteritems():
+-            req.headers[k] = v
+-        res = req.get_response(self.api)
+-        self.assertEqual(400, res.status_int)
+-
+-        fixture_headers = {'x-image-meta-store': 'file',
+-                           'x-image-meta-disk-format': 'vhd',
+-                           'x-image-meta-location': 'swift+config://xxx',
+-                           'x-image-meta-container-format': 'ovf',
+-                           'x-image-meta-name': 'fake image #F'}
+-
+-        req = webob.Request.blank("/images")
+-        req.headers['Content-Type'] = 'application/octet-stream'
+-        req.method = 'POST'
+-        for k, v in fixture_headers.iteritems():
+-            req.headers[k] = v
+-        res = req.get_response(self.api)
+-        self.assertEqual(400, res.status_int)
+-
+     def test_add_copy_from_with_location(self):
+         """Tests creates an image from copy-from and location"""
+         fixture_headers = {'x-image-meta-store': 'file',
+@@ -1025,34 +978,6 @@ class TestGlanceAPI(base.IsolatedUnitTest):
+         res = req.get_response(self.api)
+         self.assertEqual(res.status_int, 400)
+ 
+-    def test_add_copy_from_with_restricted_sources(self):
+-        """Tests creates an image from copy-from with restricted sources"""
+-        fixture_headers = {'x-image-meta-store': 'file',
+-                           'x-image-meta-disk-format': 'vhd',
+-                           'x-glance-api-copy-from': 'file:///etc/passwd',
+-                           'x-image-meta-container-format': 'ovf',
+-                           'x-image-meta-name': 'fake image #F'}
+-
+-        req = webob.Request.blank("/images")
+-        req.method = 'POST'
+-        for k, v in six.iteritems(fixture_headers):
+-            req.headers[k] = v
+-        res = req.get_response(self.api)
+-        self.assertEqual(400, res.status_int)
+-
+-        fixture_headers = {'x-image-meta-store': 'file',
+-                           'x-image-meta-disk-format': 'vhd',
+-                           'x-glance-api-copy-from': 'swift+config://xxx',
+-                           'x-image-meta-container-format': 'ovf',
+-                           'x-image-meta-name': 'fake image #F'}
+-
+-        req = webob.Request.blank("/images")
+-        req.method = 'POST'
+-        for k, v in six.iteritems(fixture_headers):
+-            req.headers[k] = v
+-        res = req.get_response(self.api)
+-        self.assertEqual(400, res.status_int)
+-
+     def test_add_copy_from_upload_image_unauthorized_with_body(self):
+         rules = {"upload_image": '!', "modify_image": '@',
+                  "add_image": '@'}
diff --git a/0008-To-prevent-client-use-v2-patch-api-to-handle-file-an.patch b/0008-To-prevent-client-use-v2-patch-api-to-handle-file-an.patch
new file mode 100644
index 0000000..d80fddc
--- /dev/null
+++ b/0008-To-prevent-client-use-v2-patch-api-to-handle-file-an.patch
@@ -0,0 +1,670 @@
+From 0b637884baaea4cefc46dc59314d54c57f567921 Mon Sep 17 00:00:00 2001
+From: Zhi Yan Liu <zhiyanl at cn.ibm.com>
+Date: Mon, 15 Dec 2014 12:29:55 +0800
+Subject: [PATCH] To prevent client use v2 patch api to handle file and swift
+ location
+
+The change will be used to restrict client to download and delete any
+file in glance-api server. The same resone and logic as what we did in
+v1:
+https://github.com/openstack/glance/blob/master/glance/api/v1/images.py#L429
+
+Closes-Bug: bug/1400966
+DocImpact
+
+Note: Even this change could fully resolve the problem for Glance, but
+we still need to fix this issue from glance_store perspective
+separatelly due to other projects can use the lib directly.
+
+Conflicts:
+	glance/api/v1/images.py
+	glance/common/store_utils.py
+	glance/location.py
+	glance/tests/functional/v1/test_copy_to_file.py
+	glance/tests/functional/v2/test_images.py
+	glance/tests/unit/test_store_image.py
+	glance/tests/unit/test_store_location.py
+	glance/tests/unit/utils.py
+	glance/tests/unit/v1/test_api.py
+
+(cherry picked from commit 4afdb017aa1ccef01482f117cb8d0736a6da38ed)
+Signed-off-by: Zhi Yan Liu <zhiyanl at cn.ibm.com>
+
+Change-Id: I5a594731f18eb4dd4fba56de766e5c1c0e0cc442
+Resolves: rhbz #1174483
+Resolves: rhbz #1174484
+Upstream-Closes-Bug: #1400966
+Upstream-Kilo: https://review.openstack.org/#/c/141706/
+Upstream-Juno: https://review.openstack.org/#/c/142373/
+Upstream-Icehouse: https://review.openstack.org/#/c/142703/
+Upstream-change-Id: I72dbead3cb2dcb87f52658ddb880e26880cc229b
+Reviewed-on: https://code.engineering.redhat.com/gerrit/38836
+Reviewed-by: Eoghan Glynn <eglynn at redhat.com>
+Tested-by: Eoghan Glynn <eglynn at redhat.com>
+Reviewed-on: https://code.engineering.redhat.com/gerrit/39077
+Reviewed-by: Lon Hohberger <lhh at redhat.com>
+Tested-by: Lon Hohberger <lhh at redhat.com>
+---
+ glance/api/v1/images.py                         |  27 ++--
+ glance/store/__init__.py                        |  30 ++++-
+ glance/tests/functional/v1/test_copy_to_file.py |  30 ++++-
+ glance/tests/functional/v2/test_images.py       | 168 +++++++++++-------------
+ glance/tests/unit/test_store_image.py           |   3 +-
+ glance/tests/unit/test_store_location.py        |  30 ++++-
+ glance/tests/unit/utils.py                      |   9 +-
+ glance/tests/unit/v1/test_api.py                |  77 ++++++++++-
+ 8 files changed, 251 insertions(+), 123 deletions(-)
+
+diff --git a/glance/api/v1/images.py b/glance/api/v1/images.py
+index dd6ec06..2f8afa0 100644
+--- a/glance/api/v1/images.py
++++ b/glance/api/v1/images.py
+@@ -48,6 +48,7 @@ from glance.store import get_known_schemes
+ from glance.store import get_size_from_backend
+ from glance.store import get_store_from_location
+ from glance.store import get_store_from_scheme
++from glance.store import validate_external_location
+ 
+ LOG = logging.getLogger(__name__)
+ SUPPORTED_PARAMS = glance.api.v1.SUPPORTED_PARAMS
+@@ -404,23 +405,19 @@ class Controller(controller.BaseController):
+     @staticmethod
+     def _validate_source(source, req):
+         """
+-        External sources (as specified via the location or copy-from headers)
+-        are supported only over non-local store types, i.e. S3, Swift, HTTP.
+-        Note the absence of file:// for security reasons, see LP bug #942118.
+-        If the above constraint is violated, we reject with 400 "Bad Request".
++        To validate if external sources (as specified via the location
++        or copy-from headers) are supported. Otherwise we reject
++        with 400 "Bad Request".
+         """
+         if source:
+-            pieces = urlparse.urlparse(source)
+-            schemes = [scheme for scheme in get_known_schemes()
+-                       if scheme != 'file']
+-            for scheme in schemes:
+-                if pieces.scheme == scheme:
+-                    return source
+-            msg = _("External sourcing not supported for store %s") % source
+-            LOG.debug(msg)
+-            raise HTTPBadRequest(explanation=msg,
+-                                 request=req,
+-                                 content_type="text/plain")
++            if validate_external_location(source):
++                return source
++            else:
++                msg = _("External source are not supported: '%s'") % source
++                LOG.debug(msg)
++                raise HTTPBadRequest(explanation=msg,
++                                     request=req,
++                                     content_type="text/plain")
+ 
+     @staticmethod
+     def _copy_from(req):
+diff --git a/glance/store/__init__.py b/glance/store/__init__.py
+index c73d3da..2ae8caa 100644
+--- a/glance/store/__init__.py
++++ b/glance/store/__init__.py
+@@ -19,6 +19,7 @@ import sys
+ 
+ from oslo.config import cfg
+ import six
++import six.moves.urllib.parse as urlparse
+ 
+ from glance.common import exception
+ from glance.common import utils
+@@ -419,6 +420,24 @@ def set_acls(context, location_uri, public=False, read_tenants=[],
+         LOG.debug(_("Skipping store.set_acls... not implemented."))
+ 
+ 
++def validate_external_location(uri):
++    """
++    Validate if URI of external location are supported.
++
++    Only over non-local store types are OK, i.e. S3, Swift,
++    HTTP. Note the absence of 'file://' for security reasons,
++    see LP bug #942118, 1400966, 'swift+config://' is also
++    absent for security reasons, see LP bug #1334196.
++
++    :param uri: The URI of external image location.
++    :return: Whether given URI of external image location are OK.
++    """
++    pieces = urlparse.urlparse(uri)
++    valid_schemes = [scheme for scheme in get_known_schemes()
++                     if scheme != 'file' and scheme != 'swift+config']
++    return pieces.scheme in valid_schemes
++
++
+ class ImageRepoProxy(glance.domain.proxy.Repo):
+ 
+     def __init__(self, image_repo, context, store_api):
+@@ -451,22 +470,23 @@ class ImageRepoProxy(glance.domain.proxy.Repo):
+ 
+ 
+ def _check_location_uri(context, store_api, uri):
+-    """
+-    Check if an image location uri is valid.
++    """Check if an image location is valid.
+ 
+     :param context: Glance request context
+     :param store_api: store API module
+     :param uri: location's uri string
+     """
++
+     is_ok = True
+     try:
+-        size = store_api.get_size_from_backend(context, uri)
+         # NOTE(zhiyan): Some stores return zero when it catch exception
+-        is_ok = size > 0
++        is_ok = (store_api.validate_external_location(uri) and
++                 store_api.get_size_from_backend(uri, context=context) > 0)
+     except (exception.UnknownScheme, exception.NotFound):
+         is_ok = False
+     if not is_ok:
+-        raise exception.BadStoreUri(_('Invalid location: %s') % uri)
++        reason = _('Invalid location')
++        raise exception.BadStoreUri(message=reason)
+ 
+ 
+ def _check_image_location(context, store_api, location):
+diff --git a/glance/tests/functional/v1/test_copy_to_file.py b/glance/tests/functional/v1/test_copy_to_file.py
+index ae2c320..2c5d833 100644
+--- a/glance/tests/functional/v1/test_copy_to_file.py
++++ b/glance/tests/functional/v1/test_copy_to_file.py
+@@ -248,9 +248,35 @@ class TestCopyToFile(functional.FunctionalTest):
+         path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
+         http = httplib2.Http()
+         response, content = http.request(path, 'POST', headers=headers)
+-        self.assertEqual(response.status, 400, content)
++        self.assertEqual(400, response.status, content)
+ 
+-        expected = 'External sourcing not supported for store ' + copy_from
++        expected = 'External source are not supported: \'%s\'' % copy_from
++        msg = 'expected "%s" in "%s"' % (expected, content)
++        self.assertTrue(expected in content, msg)
++
++        self.stop_servers()
++
++    @skip_if_disabled
++    def test_copy_from_swift_config(self):
++        """
++        Ensure we can't copy from swift+config
++        """
++        self.cleanup()
++
++        self.start_servers(**self.__dict__.copy())
++
++        # POST /images with public image copied from file (to file)
++        headers = {'X-Image-Meta-Name': 'copied',
++                   'X-Image-Meta-disk_format': 'raw',
++                   'X-Image-Meta-container_format': 'ovf',
++                   'X-Image-Meta-Is-Public': 'True',
++                   'X-Glance-API-Copy-From': 'swift+config://xxx'}
++        path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
++        http = httplib2.Http()
++        response, content = http.request(path, 'POST', headers=headers)
++        self.assertEqual(400, response.status, content)
++
++        expected = 'External source are not supported: \'swift+config://xxx\''
+         msg = 'expected "%s" in "%s"' % (expected, content)
+         self.assertTrue(expected in content, msg)
+ 
+diff --git a/glance/tests/functional/v2/test_images.py b/glance/tests/functional/v2/test_images.py
+index 4247434..36c986e 100644
+--- a/glance/tests/functional/v2/test_images.py
++++ b/glance/tests/functional/v2/test_images.py
+@@ -15,7 +15,6 @@
+ 
+ import os
+ import signal
+-import tempfile
+ import uuid
+ 
+ import requests
+@@ -38,6 +37,19 @@ class TestImages(functional.FunctionalTest):
+         self.cleanup()
+         self.api_server.deployment_flavor = 'noauth'
+         self.start_servers(**self.__dict__.copy())
++        for i in range(3):
++            ret = test_http.http_server("foo_image_id%d" % i,
++                                        "foo_image%d" % i)
++            setattr(self, 'http_server%d_pid' % i, ret[0])
++            setattr(self, 'http_port%d' % i, ret[1])
++
++    def tearDown(self):
++        for i in range(3):
++            pid = getattr(self, 'http_server%d_pid' % i, None)
++            if pid:
++                os.kill(pid, signal.SIGKILL)
++
++        super(TestImages, self).tearDown()
+ 
+     def _url(self, path):
+         return 'http://127.0.0.1:%d%s' % (self.api_port, path)
+@@ -282,21 +294,15 @@ class TestImages(functional.FunctionalTest):
+         self.assertEqual(413, response.status_code, response.text)
+ 
+         # Adding 3 image locations should fail since configured limit is 2
+-        for i in range(3):
+-            file_path = os.path.join(self.test_dir, 'fake_image_%i' % i)
+-            with open(file_path, 'w') as fap:
+-                fap.write('glance')
+-
+         path = self._url('/v2/images/%s' % image_id)
+         media_type = 'application/openstack-images-v2.1-json-patch'
+         headers = self._headers({'content-type': media_type})
+         changes = []
+         for i in range(3):
++            url = ('http://127.0.0.1:%s/foo_image' %
++                   getattr(self, 'http_port%d' % i))
+             changes.append({'op': 'add', 'path': '/locations/-',
+-                            'value': {'url': 'file://{0}'.format(
+-                                os.path.join(self.test_dir,
+-                                             'fake_image_%i' % i)),
+-                                      'metadata': {}},
++                            'value': {'url': url, 'metadata': {}},
+                             })
+ 
+         data = jsonutils.dumps(changes)
+@@ -1811,17 +1817,14 @@ class TestImages(functional.FunctionalTest):
+         self.assertNotIn('size', image)
+         self.assertNotIn('virtual_size', image)
+ 
+-        file_path = os.path.join(self.test_dir, 'fake_image')
+-        with open(file_path, 'w') as fap:
+-            fap.write('glance')
+-
+         # Update locations for the queued image
+         path = self._url('/v2/images/%s' % image_id)
+         media_type = 'application/openstack-images-v2.1-json-patch'
+         headers = self._headers({'content-type': media_type})
++        url = 'http://127.0.0.1:%s/foo_image' % self.http_port0
+         data = jsonutils.dumps([{'op': 'replace', 'path': '/locations',
+-                                 'value': [{'url': 'file://' + file_path,
+-                                            'metadata': {}}]}])
++                                 'value': [{'url': url, 'metadata': {}}]
++                                 }])
+         response = requests.patch(path, headers=headers, data=data)
+         self.assertEqual(200, response.status_code, response.text)
+ 
+@@ -1830,7 +1833,50 @@ class TestImages(functional.FunctionalTest):
+         response = requests.get(path, headers=headers)
+         self.assertEqual(200, response.status_code)
+         image = jsonutils.loads(response.text)
+-        self.assertEqual(image['size'], 6)
++        self.assertEqual(10, image['size'])
++
++    def test_update_locations_with_restricted_sources(self):
++        self.start_servers(**self.__dict__.copy())
++        # Create an image
++        path = self._url('/v2/images')
++        headers = self._headers({'content-type': 'application/json'})
++        data = jsonutils.dumps({'name': 'image-1', 'disk_format': 'aki',
++                                'container_format': 'aki'})
++        response = requests.post(path, headers=headers, data=data)
++        self.assertEqual(201, response.status_code)
++
++        # Returned image entity should have a generated id and status
++        image = jsonutils.loads(response.text)
++        image_id = image['id']
++        self.assertEqual('queued', image['status'])
++        self.assertIsNone(image['size'])
++        self.assertIsNone(image['virtual_size'])
++
++        # Update locations for the queued image
++        path = self._url('/v2/images/%s' % image_id)
++        media_type = 'application/openstack-images-v2.1-json-patch'
++        headers = self._headers({'content-type': media_type})
++        data = jsonutils.dumps([{'op': 'replace', 'path': '/locations',
++                                 'value': [{'url': 'file:///foo_image',
++                                            'metadata': {}}]
++                                 }])
++        response = requests.patch(path, headers=headers, data=data)
++        self.assertEqual(400, response.status_code, response.text)
++
++        data = jsonutils.dumps([{'op': 'replace', 'path': '/locations',
++                                 'value': [{'url': 'swift+config:///foo_image',
++                                            'metadata': {}}]
++                                 }])
++        response = requests.patch(path, headers=headers, data=data)
++        self.assertEqual(400, response.status_code, response.text)
++
++
++class TestImagesWithRegistry(TestImages):
++    def setUp(self):
++        super(TestImagesWithRegistry, self).setUp()
++        self.api_server.data_api = (
++            'glance.tests.functional.v2.registry_data_api')
++        self.registry_server.deployment_flavor = 'trusted-auth'
+ 
+ 
+ class TestImageDirectURLVisibility(functional.FunctionalTest):
+@@ -2040,16 +2086,17 @@ class TestImageLocationSelectionStrategy(functional.FunctionalTest):
+         super(TestImageLocationSelectionStrategy, self).setUp()
+         self.cleanup()
+         self.api_server.deployment_flavor = 'noauth'
+-        self.foo_image_file = tempfile.NamedTemporaryFile()
+-        self.foo_image_file.write("foo image file")
+-        self.foo_image_file.flush()
+-        self.addCleanup(self.foo_image_file.close)
+-        ret = test_http.http_server("foo_image_id", "foo_image")
+-        self.http_server_pid, self.http_port = ret
++        for i in range(3):
++            ret = test_http.http_server("foo_image_id%d" % i,
++                                        "foo_image%d" % i)
++            setattr(self, 'http_server%d_pid' % i, ret[0])
++            setattr(self, 'http_port%d' % i, ret[1])
+ 
+     def tearDown(self):
+-        if self.http_server_pid is not None:
+-            os.kill(self.http_server_pid, signal.SIGKILL)
++        for i in range(3):
++            pid = getattr(self, 'http_server%d_pid' % i, None)
++            if pid:
++                os.kill(pid, signal.SIGKILL)
+ 
+         super(TestImageLocationSelectionStrategy, self).tearDown()
+ 
+@@ -2098,73 +2145,14 @@ class TestImageLocationSelectionStrategy(functional.FunctionalTest):
+         self.assertTrue('locations' in image)
+         self.assertTrue(image["locations"] == [])
+ 
+-       # Update image locations via PATCH
+-        path = self._url('/v2/images/%s' % image_id)
+-        media_type = 'application/openstack-images-v2.1-json-patch'
+-        headers = self._headers({'content-type': media_type})
+-        values = [{'url': 'file://%s' % self.foo_image_file.name,
+-                   'metadata': {'idx': '1'}},
+-                  {'url': 'http://127.0.0.1:%s/foo_image' % self.http_port,
+-                   'metadata': {'idx': '0'}}]
+-        doc = [{'op': 'replace',
+-                'path': '/locations',
+-                'value': values}]
+-        data = jsonutils.dumps(doc)
+-        response = requests.patch(path, headers=headers, data=data)
+-        self.assertEqual(200, response.status_code)
+-
+-        # Image locations should be visible
+-        path = self._url('/v2/images/%s' % image_id)
+-        headers = self._headers({'Content-Type': 'application/json'})
+-        response = requests.get(path, headers=headers)
+-        self.assertEqual(200, response.status_code)
+-        image = jsonutils.loads(response.text)
+-        self.assertTrue('locations' in image)
+-        self.assertEqual(image['locations'], values)
+-        self.assertTrue('direct_url' in image)
+-        self.assertEqual(image['direct_url'], values[0]['url'])
+-
+-        self.stop_servers()
+-
+-    def test_image_locatons_with_store_type_strategy(self):
+-        self.api_server.show_image_direct_url = True
+-        self.api_server.show_multiple_locations = True
+-        self.image_location_quota = 10
+-        self.api_server.location_strategy = 'store_type'
+-        preference = "http, swift, filesystem"
+-        self.api_server.store_type_location_strategy_preference = preference
+-        self.start_servers(**self.__dict__.copy())
+-
+-        # Create an image
+-        path = self._url('/v2/images')
+-        headers = self._headers({'content-type': 'application/json'})
+-        data = jsonutils.dumps({'name': 'image-1', 'type': 'kernel',
+-                                'foo': 'bar', 'disk_format': 'aki',
+-                                'container_format': 'aki'})
+-        response = requests.post(path, headers=headers, data=data)
+-        self.assertEqual(201, response.status_code)
+-
+-        # Get the image id
+-        image = jsonutils.loads(response.text)
+-        image_id = image['id']
+-
+-        # Image locations should not be visible before location is set
+-        path = self._url('/v2/images/%s' % image_id)
+-        headers = self._headers({'Content-Type': 'application/json'})
+-        response = requests.get(path, headers=headers)
+-        self.assertEqual(200, response.status_code)
+-        image = jsonutils.loads(response.text)
+-        self.assertTrue('locations' in image)
+-        self.assertTrue(image["locations"] == [])
+-
+-       # Update image locations via PATCH
++        # Update image locations via PATCH
+         path = self._url('/v2/images/%s' % image_id)
+         media_type = 'application/openstack-images-v2.1-json-patch'
+         headers = self._headers({'content-type': media_type})
+-        values = [{'url': 'file://%s' % self.foo_image_file.name,
+-                   'metadata': {'idx': '1'}},
+-                  {'url': 'http://127.0.0.1:%s/foo_image' % self.http_port,
+-                   'metadata': {'idx': '0'}}]
++        values = [{'url': 'http://127.0.0.1:%s/foo_image' % self.http_port0,
++                   'metadata': {}},
++                  {'url': 'http://127.0.0.1:%s/foo_image' % self.http_port1,
++                   'metadata': {}}]
+         doc = [{'op': 'replace',
+                 'path': '/locations',
+                 'value': values}]
+@@ -2172,8 +2160,6 @@ class TestImageLocationSelectionStrategy(functional.FunctionalTest):
+         response = requests.patch(path, headers=headers, data=data)
+         self.assertEqual(200, response.status_code)
+ 
+-        values.sort(key=lambda loc: int(loc['metadata']['idx']))
+-
+         # Image locations should be visible
+         path = self._url('/v2/images/%s' % image_id)
+         headers = self._headers({'Content-Type': 'application/json'})
+diff --git a/glance/tests/unit/test_store_image.py b/glance/tests/unit/test_store_image.py
+index 424915b..9d6cc4e 100644
+--- a/glance/tests/unit/test_store_image.py
++++ b/glance/tests/unit/test_store_image.py
+@@ -16,6 +16,7 @@ import mox
+ 
+ from glance.common import exception
+ import glance.store
++from glance.tests.unit import base as unit_test_base
+ from glance.tests.unit import utils as unit_test_utils
+ from glance.tests import utils
+ 
+@@ -731,7 +732,7 @@ class TestStoreImageRepo(utils.BaseTestCase):
+         self.assertEqual(acls['read'], [TENANT2])
+ 
+ 
+-class TestImageFactory(utils.BaseTestCase):
++class TestImageFactory(unit_test_base.StoreClearingUnitTest):
+ 
+     def setUp(self):
+         super(TestImageFactory, self).setUp()
+diff --git a/glance/tests/unit/test_store_location.py b/glance/tests/unit/test_store_location.py
+index df8d5d7..a19a33a 100644
+--- a/glance/tests/unit/test_store_location.py
++++ b/glance/tests/unit/test_store_location.py
+@@ -488,11 +488,12 @@ class TestStoreLocation(base.StoreClearingUnitTest):
+                               ctx,
+                               store)
+ 
++    class FakeImageProxy():
++        size = None
++        context = None
++        store_api = mock.Mock()
++
+     def test_add_location_for_image_without_size(self):
+-        class FakeImageProxy():
+-            size = None
+-            context = None
+-            store_api = mock.Mock()
+ 
+         def fake_get_size_from_backend(context, uri):
+             return 1
+@@ -504,14 +505,31 @@ class TestStoreLocation(base.StoreClearingUnitTest):
+             loc2 = {'url': 'file:///fake2.img.tar.gz', 'metadata': {}}
+ 
+             # Test for insert location
+-            image1 = FakeImageProxy()
++            image1 = TestStoreLocation.FakeImageProxy()
+             locations = glance.store.StoreLocations(image1, [])
+             locations.insert(0, loc2)
+             self.assertEqual(image1.size, 1)
+ 
+             # Test for set_attr of _locations_proxy
+-            image2 = FakeImageProxy()
++            image2 = TestStoreLocation.FakeImageProxy()
+             locations = glance.store.StoreLocations(image2, [loc1])
+             locations[0] = loc2
+             self.assertTrue(loc2 in locations)
+             self.assertEqual(image2.size, 1)
++
++    def test_add_location_with_restricted_sources(self):
++
++        loc1 = {'url': 'file:///fake1.img.tar.gz', 'metadata': {}}
++        loc2 = {'url': 'swift+config:///xxx', 'metadata': {}}
++
++        # Test for insert location
++        image1 = TestStoreLocation.FakeImageProxy()
++        locations = glance.store.StoreLocations(image1, [])
++        self.assertRaises(exception.BadStoreUri, locations.insert, 0, loc1)
++        self.assertNotIn(loc1, locations)
++
++        # Test for set_attr of _locations_proxy
++        image2 = TestStoreLocation.FakeImageProxy()
++        locations = glance.store.StoreLocations(image2, [loc1])
++        self.assertRaises(exception.BadStoreUri, locations.insert, 0, loc2)
++        self.assertNotIn(loc2, locations)
+diff --git a/glance/tests/unit/utils.py b/glance/tests/unit/utils.py
+index 1c4e16a..e0dfc43 100644
+--- a/glance/tests/unit/utils.py
++++ b/glance/tests/unit/utils.py
+@@ -14,9 +14,9 @@
+ #    under the License.
+ 
+ import urllib
+-import urlparse
+ 
+ from oslo.config import cfg
++import six.moves.urllib.parse as urlparse
+ 
+ from glance.common import exception
+ from glance.common import wsgi
+@@ -113,7 +113,6 @@ class FakeDB(object):
+     def __getattr__(self, key):
+         return getattr(simple_db, key)
+ 
+-
+ class FakeStoreAPI(object):
+     def __init__(self, store_metadata=None):
+         self.data = {
+@@ -188,6 +187,12 @@ class FakeStoreAPI(object):
+     def check_location_metadata(self, val, key=''):
+         glance.store.check_location_metadata(val)
+ 
++    def validate_external_location(self, uri):
++        if uri and urlparse.urlparse(uri).scheme:
++            return glance.store.validate_external_location(uri)
++        else:
++            return True
++
+ 
+ class FakePolicyEnforcer(object):
+     def __init__(self, *_args, **kwargs):
+diff --git a/glance/tests/unit/v1/test_api.py b/glance/tests/unit/v1/test_api.py
+index 5618cb0..9856487 100644
+--- a/glance/tests/unit/v1/test_api.py
++++ b/glance/tests/unit/v1/test_api.py
+@@ -379,7 +379,7 @@ class TestGlanceAPI(base.IsolatedUnitTest):
+ 
+         res = req.get_response(self.api)
+         self.assertEqual(res.status_int, 400)
+-        self.assertTrue('External sourcing not supported' in res.body)
++        self.assertIn('External source are not supported', res.body)
+ 
+     def test_create_with_location_bad_store_uri(self):
+         fixture_headers = {
+@@ -962,6 +962,53 @@ class TestGlanceAPI(base.IsolatedUnitTest):
+             res = req.get_response(self.api)
+             self.assertEqual(res.status_int, 409)
+ 
++    def test_add_location_with_invalid_location_on_conflict_image_size(self):
++        """Tests creates an image from location and conflict image size"""
++        fixture_headers = {'x-image-meta-store': 'file',
++                           'x-image-meta-disk-format': 'vhd',
++                           'x-image-meta-location': 'http://a/b/c.tar.gz',
++                           'x-image-meta-container-format': 'ovf',
++                           'x-image-meta-name': 'fake image #F',
++                           'x-image-meta-size': '1'}
++
++        req = webob.Request.blank("/images")
++        req.headers['Content-Type'] = 'application/octet-stream'
++        req.method = 'POST'
++        for k, v in fixture_headers.iteritems():
++            req.headers[k] = v
++        res = req.get_response(self.api)
++        self.assertEqual(400, res.status_int)
++
++    def test_add_location_with_invalid_location_on_restricted_sources(self):
++        """Tests creates an image from location and restricted sources"""
++        fixture_headers = {'x-image-meta-store': 'file',
++                           'x-image-meta-disk-format': 'vhd',
++                           'x-image-meta-location': 'file:///etc/passwd',
++                           'x-image-meta-container-format': 'ovf',
++                           'x-image-meta-name': 'fake image #F'}
++
++        req = webob.Request.blank("/images")
++        req.headers['Content-Type'] = 'application/octet-stream'
++        req.method = 'POST'
++        for k, v in fixture_headers.iteritems():
++            req.headers[k] = v
++        res = req.get_response(self.api)
++        self.assertEqual(400, res.status_int)
++
++        fixture_headers = {'x-image-meta-store': 'file',
++                           'x-image-meta-disk-format': 'vhd',
++                           'x-image-meta-location': 'swift+config://xxx',
++                           'x-image-meta-container-format': 'ovf',
++                           'x-image-meta-name': 'fake image #F'}
++
++        req = webob.Request.blank("/images")
++        req.headers['Content-Type'] = 'application/octet-stream'
++        req.method = 'POST'
++        for k, v in fixture_headers.iteritems():
++            req.headers[k] = v
++        res = req.get_response(self.api)
++        self.assertEqual(400, res.status_int)
++
+     def test_add_copy_from_with_location(self):
+         """Tests creates an image from copy-from and location"""
+         fixture_headers = {'x-image-meta-store': 'file',
+@@ -978,6 +1025,34 @@ class TestGlanceAPI(base.IsolatedUnitTest):
+         res = req.get_response(self.api)
+         self.assertEqual(res.status_int, 400)
+ 
++    def test_add_copy_from_with_restricted_sources(self):
++        """Tests creates an image from copy-from with restricted sources"""
++        fixture_headers = {'x-image-meta-store': 'file',
++                           'x-image-meta-disk-format': 'vhd',
++                           'x-glance-api-copy-from': 'file:///etc/passwd',
++                           'x-image-meta-container-format': 'ovf',
++                           'x-image-meta-name': 'fake image #F'}
++
++        req = webob.Request.blank("/images")
++        req.method = 'POST'
++        for k, v in six.iteritems(fixture_headers):
++            req.headers[k] = v
++        res = req.get_response(self.api)
++        self.assertEqual(400, res.status_int)
++
++        fixture_headers = {'x-image-meta-store': 'file',
++                           'x-image-meta-disk-format': 'vhd',
++                           'x-glance-api-copy-from': 'swift+config://xxx',
++                           'x-image-meta-container-format': 'ovf',
++                           'x-image-meta-name': 'fake image #F'}
++
++        req = webob.Request.blank("/images")
++        req.method = 'POST'
++        for k, v in six.iteritems(fixture_headers):
++            req.headers[k] = v
++        res = req.get_response(self.api)
++        self.assertEqual(400, res.status_int)
++
+     def test_add_copy_from_upload_image_unauthorized_with_body(self):
+         rules = {"upload_image": '!', "modify_image": '@',
+                  "add_image": '@'}
diff --git a/0009-Prevent-file-swift-config-and-filesystem-schemes.patch b/0009-Prevent-file-swift-config-and-filesystem-schemes.patch
new file mode 100644
index 0000000..4b19967
--- /dev/null
+++ b/0009-Prevent-file-swift-config-and-filesystem-schemes.patch
@@ -0,0 +1,133 @@
+From 0e47acd351d0a4793e0cce08b1958ad6129bd3fd Mon Sep 17 00:00:00 2001
+From: Grant Murphy <grant.murphy at hp.com>
+Date: Wed, 7 Jan 2015 16:09:38 -0800
+Subject: [PATCH] Prevent file, swift+config and filesystem schemes
+
+This change ensures that 'file', 'filesystem', and 'swift+config' URI
+schemes are not allowed when setting the location field. A previous
+fix to CVE-2014-9493 attempted to address this issue but did not
+include 'filesystem', a URI scheme allowed by the glance_store.
+
+Without this fix in place it is possible for a client to access any file
+the glance-api server has read permissions for.
+
+(cherry picked from commit 5191ed1879c5fd5b2694f922bcedec232f461088)
+
+Conflicts:
+	glance/common/store_utils.py
+
+Change-Id: Ib92f464331b7bab9ca49f164102313d8652dc723
+Resolves: rhbz #1174483
+Resolves: rhbz #1174484
+Upstream-Closes-Bug: #1408663
+Upstream-Kilo: https://review.openstack.org/#/c/145640/
+Upstream-Juno: https://review.openstack.org/#/c/145916/
+Upstream-Icehouse: https://review.openstack.org/#/c/145974/
+Upstream-change-Id: I02cd099a8634b9c7e3cf8f172bcbd33f8edcbc83
+Reviewed-on: https://code.engineering.redhat.com/gerrit/39653
+Reviewed-by: Jon Bernard <jobernar at redhat.com>
+Reviewed-by: Flavio Percoco <fpercoco at redhat.com>
+Tested-by: Flavio Percoco <fpercoco at redhat.com>
+---
+ glance/store/__init__.py                 | 11 +++++++----
+ glance/tests/unit/test_store_location.py |  3 +++
+ glance/tests/unit/v1/test_api.py         | 32 ++++++++++++--------------------
+ 3 files changed, 22 insertions(+), 24 deletions(-)
+
+diff --git a/glance/store/__init__.py b/glance/store/__init__.py
+index 2ae8caa..d158f97 100644
+--- a/glance/store/__init__.py
++++ b/glance/store/__init__.py
+@@ -74,6 +74,8 @@ _ALL_STORES = [
+     'glance.store.gridfs.Store',
+ ]
+ 
++RESTRICTED_URI_SCHEMAS = frozenset(['file', 'filesystem', 'swift+config'])
++
+ 
+ class BackendException(Exception):
+     pass
+@@ -432,10 +434,11 @@ def validate_external_location(uri):
+     :param uri: The URI of external image location.
+     :return: Whether given URI of external image location are OK.
+     """
+-    pieces = urlparse.urlparse(uri)
+-    valid_schemes = [scheme for scheme in get_known_schemes()
+-                     if scheme != 'file' and scheme != 'swift+config']
+-    return pieces.scheme in valid_schemes
++
++    # TODO(gm): Use a whitelist of allowed schemes
++    scheme = urlparse.urlparse(uri).scheme
++    return (scheme in get_known_schemes() and
++            scheme not in RESTRICTED_URI_SCHEMAS)
+ 
+ 
+ class ImageRepoProxy(glance.domain.proxy.Repo):
+diff --git a/glance/tests/unit/test_store_location.py b/glance/tests/unit/test_store_location.py
+index a19a33a..d64b783 100644
+--- a/glance/tests/unit/test_store_location.py
++++ b/glance/tests/unit/test_store_location.py
+@@ -521,12 +521,15 @@ class TestStoreLocation(base.StoreClearingUnitTest):
+ 
+         loc1 = {'url': 'file:///fake1.img.tar.gz', 'metadata': {}}
+         loc2 = {'url': 'swift+config:///xxx', 'metadata': {}}
++        loc3 = {'url': 'filesystem:///foo.img.tar.gz', 'metadata': {}}
+ 
+         # Test for insert location
+         image1 = TestStoreLocation.FakeImageProxy()
+         locations = glance.store.StoreLocations(image1, [])
+         self.assertRaises(exception.BadStoreUri, locations.insert, 0, loc1)
++        self.assertRaises(exception.BadStoreUri, locations.insert, 0, loc3)
+         self.assertNotIn(loc1, locations)
++        self.assertNotIn(loc3, locations)
+ 
+         # Test for set_attr of _locations_proxy
+         image2 = TestStoreLocation.FakeImageProxy()
+diff --git a/glance/tests/unit/v1/test_api.py b/glance/tests/unit/v1/test_api.py
+index 9856487..33af257 100644
+--- a/glance/tests/unit/v1/test_api.py
++++ b/glance/tests/unit/v1/test_api.py
+@@ -1027,31 +1027,23 @@ class TestGlanceAPI(base.IsolatedUnitTest):
+ 
+     def test_add_copy_from_with_restricted_sources(self):
+         """Tests creates an image from copy-from with restricted sources"""
+-        fixture_headers = {'x-image-meta-store': 'file',
++        header_template = {'x-image-meta-store': 'file',
+                            'x-image-meta-disk-format': 'vhd',
+-                           'x-glance-api-copy-from': 'file:///etc/passwd',
+                            'x-image-meta-container-format': 'ovf',
+                            'x-image-meta-name': 'fake image #F'}
+ 
+-        req = webob.Request.blank("/images")
+-        req.method = 'POST'
+-        for k, v in six.iteritems(fixture_headers):
+-            req.headers[k] = v
+-        res = req.get_response(self.api)
+-        self.assertEqual(400, res.status_int)
+-
+-        fixture_headers = {'x-image-meta-store': 'file',
+-                           'x-image-meta-disk-format': 'vhd',
+-                           'x-glance-api-copy-from': 'swift+config://xxx',
+-                           'x-image-meta-container-format': 'ovf',
+-                           'x-image-meta-name': 'fake image #F'}
++        schemas = ["file:///etc/passwd",
++                   "swift+config:///xxx",
++                   "filesystem:///etc/passwd"]
+ 
+-        req = webob.Request.blank("/images")
+-        req.method = 'POST'
+-        for k, v in six.iteritems(fixture_headers):
+-            req.headers[k] = v
+-        res = req.get_response(self.api)
+-        self.assertEqual(400, res.status_int)
++        for schema in schemas:
++            req = webob.Request.blank("/images")
++            req.method = 'POST'
++            for k, v in six.iteritems(header_template):
++                req.headers[k] = v
++            req.headers['x-glance-api-copy-from'] = schema
++            res = req.get_response(self.api)
++            self.assertEqual(400, res.status_int)
+ 
+     def test_add_copy_from_upload_image_unauthorized_with_body(self):
+         rules = {"upload_image": '!', "modify_image": '@',
diff --git a/openstack-glance.spec b/openstack-glance.spec
index 7dcc40b..10b1a8a 100644
--- a/openstack-glance.spec
+++ b/openstack-glance.spec
@@ -1,6 +1,6 @@
 Name:             openstack-glance
 Version:          2014.1.3
-Release:          2%{?dist}
+Release:          3%{?dist}
 Summary:          OpenStack Image Service
 
 Group:            Applications/System
@@ -32,6 +32,11 @@ Patch0001: 0001-Remove-runtime-dep-on-python-pbr.patch
 Patch0002: 0002-Don-t-access-the-net-while-building-docs.patch
 Patch0003: 0003-avoid-unsupported-storage-drivers.patch
 Patch0004: 0004-notify-calling-process-we-are-ready-to-serve.patch
+Patch0005: 0005-Make-rbd-store-s-pool-handling-more-universal.patch
+Patch0006: 0006-To-prevent-client-use-v2-patch-api-to-handle-file-an.patch
+Patch0007: 0007-Revert-To-prevent-client-use-v2-patch-api-to-handle-.patch
+Patch0008: 0008-To-prevent-client-use-v2-patch-api-to-handle-file-an.patch
+Patch0009: 0009-Prevent-file-swift-config-and-filesystem-schemes.patch
 
 BuildArch:        noarch
 BuildRequires:    python2-devel
@@ -134,6 +139,11 @@ This package contains documentation files for glance.
 %patch0002 -p1
 %patch0003 -p1
 %patch0004 -p1
+%patch0005 -p1
+%patch0006 -p1
+%patch0007 -p1
+%patch0008 -p1
+%patch0009 -p1
 
 # Remove bundled egg-info
 rm -rf glance.egg-info
@@ -385,10 +395,14 @@ fi
 %doc doc/build/html
 
 %changelog
+* Tue Jan 13 2015 Haikel Guemar <hguemar at fedoraproject.org> - 2014.1.3-3
+- Unrestricted traversal flow (RHBZ #1174474)
+- Make rbd store's pool handling more universal
+
 * Fri Oct 10 2014 Haikel Guemar <hguemar at fedoraproject.org> - 2014.1.3-2
 - Minor fixes to spec
 
-* Fri Oct 10 2014 Haikel Guemar <hguemar at fedoraproject.org> 2014.1.3-1
+* Fri Oct 10 2014 Haikel Guemar <hguemar at fedoraproject.org> - 2014.1.3-1
 - Update to upstream 2014.1.3
 
 * Wed Sep 10 2014 Alan Pevec <apevec at redhat.com> - 2014.1.2-5
@@ -434,35 +448,35 @@ fi
 * Mon Mar 24 2014 Pádraig Brady <pbrady at redhat.com> - 2014.1-0.4.b3
 - unconfigure unsupported storage drivers
 
-* Fri Mar 14 2014 Flavio Percoco <flavio at redhat.com> 2014.1-0.3.b3
+* Fri Mar 14 2014 Flavio Percoco <flavio at redhat.com> - 2014.1-0.3.b3
 - Update to Icehouse milestone 3
 
-* Fri Jan 31 2014 Alan Pevec <apevec at redhat.com> 2014.1-0.2.b2
+* Fri Jan 31 2014 Alan Pevec <apevec at redhat.com> - 2014.1-0.2.b2
 - Update to Icehouse milestone 2
 
-* Mon Dec 23 2013 Pádraig Brady <pbrady at redhat.com> 2014.1-0.1.b1
+* Mon Dec 23 2013 Pádraig Brady <pbrady at redhat.com> - 2014.1-0.1.b1
 - Update to Icehouse milestone 1
 
-* Fri Oct 25 2013 Flavio Percoco <flavio at redhat.com> 2013.2-2
+* Fri Oct 25 2013 Flavio Percoco <flavio at redhat.com> - 2013.2-2
 - Fixes #956815
 
-* Fri Oct 18 2013 Pádraig Brady <pbrady at redhat.com> 2013.2-1
+* Fri Oct 18 2013 Pádraig Brady <pbrady at redhat.com> - 2013.2-1
 - Update to Havana GA
 
-* Thu Oct 03 2013 Pádraig Brady <pbrady at redhat.com> 2013.2-0.12.rc1
+* Thu Oct 03 2013 Pádraig Brady <pbrady at redhat.com> - 2013.2-0.12.rc1
 - Update to 2013.2.rc1
 - Fixup various config file issues
 
-* Wed Sep 25 2013 Pádraig Brady <pbrady at redhat.com> 2013.2-0.11.b3
+* Wed Sep 25 2013 Pádraig Brady <pbrady at redhat.com> - 2013.2-0.11.b3
 - Fix up dist.conf issues
 
-* Fri Sep 20 2013 John Bresnahan <jbresnah at redhat.com> 2013.2-0.10.b3
+* Fri Sep 20 2013 John Bresnahan <jbresnah at redhat.com> - 2013.2-0.10.b3
 - Split distribution config to /usr/share/glance/glance*-dist.conf
 
-* Fri Sep 20 2013 John Bresnahan <jbresnah at redhat.com> 2013.2-0.9.b3
+* Fri Sep 20 2013 John Bresnahan <jbresnah at redhat.com> - 2013.2-0.9.b3
 - Substitute in the correct version information
 
-* Mon Sep  9 2013 John Bresnahan <jbresnah at redhat.com> 2013.2-0.8.b3
+* Mon Sep  9 2013 John Bresnahan <jbresnah at redhat.com> - 2013.2-0.8.b3
 - Update to version 2013.2.b3
 - Remove runtime dep on python pbr
 - Revert use oslo.sphinx and remove local copy of doc
@@ -470,69 +484,69 @@ fi
 * Sat Aug 03 2013 Fedora Release Engineering <rel-eng at lists.fedoraproject.org> - 2013.2-0.7.b2
 - Rebuilt for https://fedoraproject.org/wiki/Fedora_20_Mass_Rebuild
 
-* Tue Jul 23 2013 Pádraig Brady <pbrady at redhat.com> 2013.2-0.6.b2
+* Tue Jul 23 2013 Pádraig Brady <pbrady at redhat.com> - 2013.2-0.6.b2
 - Update to Havana milestone 2
 - Depend on python-keystoneclient for auth_token middleware
 - Remove tests from the distribution
 
-* Fri Jun  7 2013 John Bresnahan <jbresnah at redhat.com> 2013.2-0.3.b1
+* Fri Jun  7 2013 John Bresnahan <jbresnah at redhat.com> - 2013.2-0.3.b1
 - Don't access the net while building docs
 
-* Thu Jun  6 2013 John Bresnahan <jbresnah at redhat.com> 2013.2-0.1.b1
+* Thu Jun  6 2013 John Bresnahan <jbresnah at redhat.com> - 2013.2-0.1.b1
 - Update to version 2013.2.b1
 
-* Thu Jun  6 2013 John Bresnahan <jbresnah at redhat.com> 2013.1.2
+* Thu Jun  6 2013 John Bresnahan <jbresnah at redhat.com> - 2013.1.2
 - Update to version 2013.1.2
 
-* Mon May 13 2013 Pádraig Brady <pbrady at redhat.com> 2013.1-2
+* Mon May 13 2013 Pádraig Brady <pbrady at redhat.com> - 2013.1-2
 - Add the scrubber service for deferred image deletion
 
-* Mon Apr 08 2013 Nikola Đipanov <ndipanov at redhat.com> 2013.1-1
+* Mon Apr 08 2013 Nikola Đipanov <ndipanov at redhat.com> - 2013.1-1
 - Update to Grizzly final
 
-* Tue Apr  2 2013 Nikola Đipanov <ndipanov at redhat.com> 2013.1-0.9.rc2
+* Tue Apr  2 2013 Nikola Đipanov <ndipanov at redhat.com> - 2013.1-0.9.rc2
 - Update to Grizzly RC2
 
-* Tue Apr  2 2013 Pádraig Brady <pbrady at redhat.com> 2013.1-0.8.rc1
+* Tue Apr  2 2013 Pádraig Brady <pbrady at redhat.com> - 2013.1-0.8.rc1
 - Adjust to support sqlalchemy-0.8.0
 
-* Fri Mar 22 2013 Nikola Đipanov <ndipanov at redhat.com> 2013.1-0.7.rc1
+* Fri Mar 22 2013 Nikola Đipanov <ndipanov at redhat.com> - 2013.1-0.7.rc1
 - Update to Grizzly RC1
 
-* Tue Feb 26 2013 Nikola Đipanov <ndipanov at redhat.com> 2013.1-0.6.g3
+* Tue Feb 26 2013 Nikola Đipanov <ndipanov at redhat.com> - 2013.1-0.6.g3
 - Fix dep issues introduced by the Grizzly-3 update
 
-* Mon Feb 25 2013 Nikola Đipanov <ndipanov at redhat.com> 2013.1-0.5.g3
+* Mon Feb 25 2013 Nikola Đipanov <ndipanov at redhat.com> - 2013.1-0.5.g3
 - Update to Grizzlt milestone 3
 
 * Thu Feb 14 2013 Fedora Release Engineering <rel-eng at lists.fedoraproject.org> - 2013.1-0.4.g2
 - Rebuilt for https://fedoraproject.org/wiki/Fedora_19_Mass_Rebuild
 
-* Tue Jan 29 2013 Nikola Đipanov <ndipanov at redhat.com> 2013.1-0.3.g2
+* Tue Jan 29 2013 Nikola Đipanov <ndipanov at redhat.com> - 2013.1-0.3.g2
 - Fix backend password leak in Glance error message (CVE-2013-0212)
 
-* Fri Jan 11 2013 Nikola Đipanov <ndipanov at redhat.com> 2013.1-0.2.g2
+* Fri Jan 11 2013 Nikola Đipanov <ndipanov at redhat.com> - 2013.1-0.2.g2
 - Update to Grizzlt milestone 2
 
-* Fri Nov 23 2012 Pádraig Brady <P at draigBrady.com> 2013.1-0.1.g1
+* Fri Nov 23 2012 Pádraig Brady <P at draigBrady.com> - 2013.1-0.1.g1
 - Update to Grizzlt milestone 1
 
-* Fri Nov  9 2012 Pádraig Brady <P at draigBrady.com> 2012.2-4
+* Fri Nov  9 2012 Pádraig Brady <P at draigBrady.com> - 2012.2-4
 - Fix Glance Authentication bypass for image deletion (CVE-2012-4573)
 
-* Thu Sep 27 2012 Alan Pevec <apevec at redhat.com> 2012.2-2
+* Thu Sep 27 2012 Alan Pevec <apevec at redhat.com> - 2012.2-2
 - Update to folsom final
 
-* Wed Sep 26 2012 Alan Pevec <apevec at redhat.com> 2012.2-0.7.rc3
+* Wed Sep 26 2012 Alan Pevec <apevec at redhat.com> - 2012.2-0.7.rc3
 - Update to Folsom rc3
 
-* Tue Sep 25 2012 Alan Pevec <apevec at redhat.com> 2012.2-0.6.rc2
+* Tue Sep 25 2012 Alan Pevec <apevec at redhat.com> - 2012.2-0.6.rc2
 - Update to Folsom rc2
 
-* Fri Sep 14 2012 Alan Pevec <apevec at redhat.com> 2012.2-0.5.rc1
+* Fri Sep 14 2012 Alan Pevec <apevec at redhat.com> - 2012.2-0.5.rc1
 - Update to Folsom rc1
 
-* Thu Aug 23 2012 Alan Pevec <apevec at redhat.com> 2012.2-0.4.f3
+* Thu Aug 23 2012 Alan Pevec <apevec at redhat.com> - 2012.2-0.4.f3
 - Update to folsom-3 milestone
 - Drop old glance CLI, deprecated by python-glanceclient
 


More information about the scm-commits mailing list