Skip to content

Commit

Permalink
Deprecate setting the payload type and encoding
Browse files Browse the repository at this point in the history
Deprecate manually setting the payload_content_type and
payload_content_encoding properties of a secret.  With this CR a user of
the client only needs to provide the payload, and the client will figure
out what the correct payload_content_type and payload_content_encoding
values should be.

Setting these properties for the user lets us avoid a lot of weird
behaviors such as the one described in Bug #1419166, and also lets us
avoid errors that happen when a user mismatches the payload and an
incorrect content type.

In the interest of backwards compatibility, these properties are still
usable, but will log deprecation warnings.  They should be removed in a
future version after current users have had enough time to update their
code bases.

Change-Id: Ibfe3ad42e11bd83c002d0f1b69fb8a323a7b6f3d
Closes-Bug: #1419166
  • Loading branch information
Douglas Mendizábal committed Mar 11, 2015
1 parent 42af4f5 commit 46ef634
Show file tree
Hide file tree
Showing 4 changed files with 200 additions and 38 deletions.
17 changes: 8 additions & 9 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@ with keystone authentication:
>>> from barbicanclient import client
>>> # We'll use Keystone API v3 for authentication
>>> auth = identity.v3.Password(auth_url='http://localhost:5000/v3',
... username='admin_user',
... user_domain_name='Default',
... password='password',
... project_name='demo',
... project_domain_name='Default')
>>> auth = identity.v3.Password(auth_url=u'http://localhost:5000/v3',
... username=u'admin_user',
... user_domain_name=u'Default',
... password=u'password',
... project_name=u'demo',
... project_domain_name=u'Default')
>>> # Next we'll create a Keystone session using the auth plugin we just created
>>> sess = session.Session(auth=auth)
Expand All @@ -45,9 +45,8 @@ with keystone authentication:
>>> barbican = client.Client(session=sess)
>>> # Let's create a Secret to store some sensitive data
>>> secret = barbican.secrets.create(name='Self destruction sequence',
... payload='the magic words are squeamish ossifrage',
... payload_content_type='text/plain')
>>> secret = barbican.secrets.create(name=u'Self destruction sequence',
... payload=u'the magic words are squeamish ossifrage')
>>> # Now let's store the secret by using its store() method. This will send the secret data
>>> # to Barbican, where it will be encrypted and stored securely in the cloud.
Expand Down
58 changes: 50 additions & 8 deletions barbicanclient/secrets.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import base64
import functools
import logging
import six

from oslo_utils.timeutils import parse_isotime
import six

from barbicanclient import base
from barbicanclient import formatter
Expand Down Expand Up @@ -209,11 +210,21 @@ def payload(self, value):
@payload_content_type.setter
@immutable_after_save
def payload_content_type(self, value):
LOG.warning(
'DEPRECATION WARNING: Manually setting the payload_content_type '
'can lead to unexpected results. It will be removed in a future '
'release. See Launchpad Bug #1419166.'
)
self._payload_content_type = value

@payload_content_encoding.setter
@immutable_after_save
def payload_content_encoding(self, value):
LOG.warning(
'DEPRECATION WARNING: Manually setting the '
'payload_content_encoding can lead to unexpected results. It '
'will be removed in a future release. See Launchpad Bug #1419166.'
)
self._payload_content_encoding = value

def _fetch_payload(self):
Expand All @@ -234,15 +245,41 @@ def store(self):
"""
secret_dict = base.filter_empty_keys({
'name': self.name,
'payload': self.payload,
'payload_content_type': self.payload_content_type,
'payload_content_encoding': self.payload_content_encoding,
'algorithm': self.algorithm,
'mode': self.mode,
'bit_length': self.bit_length,
'expiration': self.expiration
})

if self.payload_content_type:
"""
Setting the payload_content_type and payload_content_encoding
manually is deprecated. This clause of the if statement is here
for backwards compatibility and should be removed in a future
release.
"""
secret_dict['payload'] = self.payload
secret_dict['payload_content_type'] = self.payload_content_type
secret_dict['payload_content_encoding'] = (
self.payload_content_encoding
)
elif type(self.payload) is six.binary_type:
"""
six.binary_type is stored as application/octet-stream
and it is base64 encoded for a one-step POST
"""
secret_dict['payload'] = (
base64.b64encode(self.payload)
).decode('UTF-8')
secret_dict['payload_content_type'] = u'application/octet-stream'
secret_dict['payload_content_encoding'] = u'base64'
elif type(self.payload) is six.text_type:
"""
six.text_type is stored as text/plain
"""
secret_dict['payload'] = self.payload
secret_dict['payload_content_type'] = u'text/plain'

LOG.debug("Request body: {0}".format(secret_dict))

# Save, store secret_ref and return
Expand Down Expand Up @@ -332,8 +369,9 @@ def get(self, secret_ref, payload_content_type=None):
Retrieve an existing Secret from Barbican
:param str secret_ref: Full HATEOAS reference to a Secret
:param str payload_content_type: Content type to use for payload
decryption
:param str payload_content_type: DEPRECATED: Content type to use for
payload decryption. Setting this can lead to unexpected results.
See Launchpad Bug #1419166.
:returns: Secret object retrieved from Barbican
:rtype: :class:`barbicanclient.secrets.Secret`
"""
Expand All @@ -356,8 +394,12 @@ def create(self, name=None, payload=None,
:param name: A friendly name for the Secret
:param payload: The unencrypted secret data
:param payload_content_type: The format/type of the secret data
:param payload_content_encoding: The encoding of the secret data
:param payload_content_type: DEPRECATED: The format/type of the secret
data. Setting this can lead to unexpected results. See Launchpad
Bug #1419166.
:param payload_content_encoding: DEPRECATED: The encoding of the secret
data. Setting this can lead to unexpected results. See Launchpad
Bug #1419166.
:param algorithm: The algorithm associated with this secret key
:param bit_length: The bit length of this secret key
:param mode: The algorithm mode used with this secret key
Expand Down
135 changes: 116 additions & 19 deletions barbicanclient/tests/test_secrets.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import base64
import json

from oslo.utils import timeutils
Expand All @@ -23,15 +23,14 @@

class SecretData(object):
def __init__(self):
self.name = 'Self destruction sequence'
self.payload = 'the magic words are squeamish ossifrage'
self.payload_content_type = 'text/plain'
self.content = 'text/plain'
self.algorithm = 'AES'
self.name = u'Self destruction sequence'
self.payload = u'the magic words are squeamish ossifrage'
self.payload_content_type = u'text/plain'
self.algorithm = u'AES'
self.created = str(timeutils.utcnow())

self.secret_dict = {'name': self.name,
'status': 'ACTIVE',
'status': u'ACTIVE',
'algorithm': self.algorithm,
'created': self.created}

Expand Down Expand Up @@ -65,17 +64,14 @@ def test_should_store_via_constructor(self):
self.responses.post(self.entity_base + '/', json=data)

secret = self.manager.create(name=self.secret.name,
payload=self.secret.payload,
payload_content_type=self.secret.content)
payload=self.secret.payload)
secret_href = secret.store()
self.assertEqual(self.entity_href, secret_href)

# Verify that correct information was sent in the call.
secret_req = json.loads(self.responses.last_request.text)
self.assertEqual(self.secret.name, secret_req['name'])
self.assertEqual(self.secret.payload, secret_req['payload'])
self.assertEqual(self.secret.payload_content_type,
secret_req['payload_content_type'])

def test_should_store_via_attributes(self):
data = {'secret_ref': self.entity_href}
Expand All @@ -84,24 +80,115 @@ def test_should_store_via_attributes(self):
secret = self.manager.create()
secret.name = self.secret.name
secret.payload = self.secret.payload
secret.payload_content_type = self.secret.content
secret_href = secret.store()
self.assertEqual(self.entity_href, secret_href)

# Verify that correct information was sent in the call.
secret_req = json.loads(self.responses.last_request.text)
self.assertEqual(self.secret.name, secret_req['name'])
self.assertEqual(self.secret.payload, secret_req['payload'])
self.assertEqual(self.secret.payload_content_type,

def test_should_store_binary_type_as_octet_stream(self):
"""
We use six.binary_type as the canonical binary type.
The client should base64 encode the payload before sending the
request.
"""
data = {'secret_ref': self.entity_href}
self.responses.post(self.entity_base + '/', json=data)

# This literal will have type(str) in Python 2, but will have
# type(bytes) in Python 3. It is six.binary_type in both cases.
binary_payload = b'F\x130\x89f\x8e\xd9\xa1\x0e\x1f\r\xf67uu\x8b'

secret = self.manager.create()
secret.name = self.secret.name
secret.payload = binary_payload
secret.store()

secret_req = json.loads(self.responses.last_request.text)
self.assertEqual(self.secret.name, secret_req['name'])
self.assertEqual(u'application/octet-stream',
secret_req['payload_content_type'])
self.assertEqual(u'base64',
secret_req['payload_content_encoding'])
self.assertNotEqual(binary_payload, secret_req['payload'])

def test_should_store_text_type_as_text_plain(self):
"""
We use six.text_type as the canonical text type.
"""
data = {'secret_ref': self.entity_href}
self.responses.post(self.entity_base + '/', json=data)

# This literal will have type(unicode) in Python 2, but will have
# type(str) in Python 3. It is six.text_type in both cases.
text_payload = u'time for an ice cold \U0001f37a'

secret = self.manager.create()
secret.payload = text_payload
secret.store()

secret_req = json.loads(self.responses.last_request.text)
self.assertEqual(text_payload, secret_req['payload'])
self.assertEqual(u'text/plain', secret_req['payload_content_type'])

def test_should_store_with_deprecated_content_type(self):
"""
DEPRECATION WARNING:
Manually setting the payload_content_type is deprecated and will be
removed in a future release.
"""
data = {'secret_ref': self.entity_href}
self.responses.post(self.entity_base + '/', json=data)

payload = 'I should be octet-stream'
payload_content_type = u'text/plain'

secret = self.manager.create()
secret.payload = payload
secret.payload_content_type = payload_content_type
secret.store()

secret_req = json.loads(self.responses.last_request.text)
self.assertEqual(payload, secret_req['payload'])
self.assertEqual(payload_content_type,
secret_req['payload_content_type'])

def test_should_store_with_deprecated_content_encoding(self):
"""
DEPRECATION WARNING:
Manually setting the payload_content_encoding is deprecated and will be
removed in a future release.
"""
data = {'secret_ref': self.entity_href}
self.responses.post(self.entity_base + '/', json=data)

encoded_payload = base64.b64encode(
b'F\x130\x89f\x8e\xd9\xa1\x0e\x1f\r\xf67uu\x8b'
).decode('UTF-8')
payload_content_type = u'application/octet-stream'
payload_content_encoding = u'base64'

secret = self.manager.create()
secret.payload = encoded_payload
secret.payload_content_type = payload_content_type
secret.payload_content_encoding = payload_content_encoding
secret.store()

secret_req = json.loads(self.responses.last_request.text)
self.assertEqual(encoded_payload, secret_req['payload'])
self.assertEqual(payload_content_type,
secret_req['payload_content_type'])
self.assertEqual(payload_content_encoding,
secret_req['payload_content_encoding'])

def test_should_be_immutable_after_submit(self):
data = {'secret_ref': self.entity_href}
self.responses.post(self.entity_base + '/', json=data)

secret = self.manager.create(name=self.secret.name,
payload=self.secret.payload,
payload_content_type=self.secret.content)
payload=self.secret.payload)
secret_href = secret.store()

self.assertEqual(self.entity_href, secret_href)
Expand Down Expand Up @@ -149,7 +236,12 @@ def test_should_get_lazy(self):
# Verify the correct URL was used to make the GET call
self.assertEqual(self.entity_href, m.last_request.url)

def test_should_get_payload_only(self):
def test_should_get_payload_only_when_content_type_is_set(self):
"""
DEPRECATION WARNING:
Manually setting the payload_content_type is deprecated and will be
removed in a future release.
"""
m = self.responses.get(self.entity_href,
request_headers={'Accept': 'application/json'},
json=self.secret.get_dict(self.entity_href))
Expand Down Expand Up @@ -182,7 +274,7 @@ def test_should_get_payload_only(self):
# Verify the correct URL was used to make the `get_raw` call
self.assertEqual(self.entity_href, n.last_request.url)

def test_should_fetch_metadata_to_get_payload_if_no_content_type_set(self):
def test_should_fetch_metadata_to_get_payload(self):
content_types_dict = {'default': 'application/octet-stream'}

data = self.secret.get_dict(self.entity_href,
Expand Down Expand Up @@ -219,7 +311,12 @@ def test_should_fetch_metadata_to_get_payload_if_no_content_type_set(self):
self.assertEqual(self.entity_href, m.last_request.url)
self.assertEqual(self.entity_href, n.last_request.url)

def test_should_decrypt_with_content_type(self):
def test_should_decrypt_when_content_type_is_set(self):
"""
DEPRECATION WARNING:
Manually setting the payload_content_type is deprecated and will be
removed in a future release.
"""
decrypted = 'decrypted text here'

request_headers = {'Accept': 'application/octet-stream'}
Expand All @@ -238,7 +335,7 @@ def test_should_decrypt_with_content_type(self):
# Verify the correct URL was used to make the call.
self.assertEqual(self.entity_href, m.last_request.url)

def test_should_decrypt_without_content_type(self):
def test_should_decrypt(self):
content_types_dict = {'default': 'application/octet-stream'}
json = self.secret.get_dict(self.entity_href, content_types_dict)
m = self.responses.get(self.entity_href,
Expand Down
Loading

0 comments on commit 46ef634

Please sign in to comment.