From 4b310083dfebe8c54e599fe319f801cca87f8dd6 Mon Sep 17 00:00:00 2001
From: Alistair Coles <alistair.coles@hp.com>
Date: Mon, 24 Aug 2015 12:34:45 +0100
Subject: [PATCH] Stop Connection class modifying os_options parameter

When a caller passes an os_options dict to the Connection class
constructor, the constructor may modify the os_options dict,
which can surprise the caller if they re-use the os_options
dict. Specifically the os_options tenant_name and object_storage_url
may be modified, and the changed values would then leak through to a
subsequent Connection constructed using the same os_options dict.

This fix simply constructs a new dict from the supplied os_options.
The patch also adds a test that covers this and also verifies that
a preauth_url passed as a keyword arg to Connection() will take
precedence over any object_storage_url in an os_options parameter.

Closes-Bug: 1488070
Change-Id: Ic6b5cf3ac68c505de155619f2610be9529e15432
---
 swiftclient/client.py          |  2 +-
 tests/unit/test_swiftclient.py | 27 +++++++++++++++++++++++++++
 2 files changed, 28 insertions(+), 1 deletion(-)

diff --git a/swiftclient/client.py b/swiftclient/client.py
index 4819c124..a2d7adab 100644
--- a/swiftclient/client.py
+++ b/swiftclient/client.py
@@ -1290,7 +1290,7 @@ class Connection(object):
         self.starting_backoff = starting_backoff
         self.max_backoff = max_backoff
         self.auth_version = auth_version
-        self.os_options = os_options or {}
+        self.os_options = dict(os_options or {})
         if tenant_name:
             self.os_options['tenant_name'] = tenant_name
         if preauthurl:
diff --git a/tests/unit/test_swiftclient.py b/tests/unit/test_swiftclient.py
index 97ae467a..23b31388 100644
--- a/tests/unit/test_swiftclient.py
+++ b/tests/unit/test_swiftclient.py
@@ -1481,6 +1481,33 @@ class TestConnection(MockHttpTest):
             ('HEAD', '/v1/AUTH_pre_url', '', {'x-auth-token': 'post_token'}),
         ])
 
+    def test_preauth_url_trumps_os_preauth_url(self):
+        storage_url = 'http://storage.example.com/v1/AUTH_pre_url'
+        os_storage_url = 'http://storage.example.com/v1/AUTH_os_pre_url'
+        os_preauth_options = {
+            'tenant_name': 'demo',
+            'object_storage_url': os_storage_url,
+        }
+        orig_os_preauth_options = dict(os_preauth_options)
+        conn = c.Connection('http://auth.example.com', 'user', 'password',
+                            os_options=os_preauth_options, auth_version=2,
+                            preauthurl=storage_url, tenant_name='not_demo')
+        fake_keystone = fake_get_auth_keystone(
+            storage_url='http://storage.example.com/v1/AUTH_post_url',
+            token='post_token')
+        fake_conn = self.fake_http_connection(200)
+        with mock.patch.multiple('swiftclient.client',
+                                 get_auth_keystone=fake_keystone,
+                                 http_connection=fake_conn,
+                                 sleep=mock.DEFAULT):
+            conn.head_account()
+        self.assertRequests([
+            ('HEAD', '/v1/AUTH_pre_url', '', {'x-auth-token': 'post_token'}),
+        ])
+
+        # check that Connection has not modified our os_options
+        self.assertEqual(orig_os_preauth_options, os_preauth_options)
+
     def test_get_auth_sets_url_and_token(self):
         with mock.patch('swiftclient.client.get_auth') as mock_get_auth:
             mock_get_auth.return_value = (