diff options
author | John Wood <john.wood@rackspace.com> | 2013-12-03 12:50:25 -0800 |
---|---|---|
committer | John Wood <john.wood@rackspace.com> | 2013-12-03 12:50:25 -0800 |
commit | 0254b25ab6d5d97320031f36d1383809a81e96f5 (patch) | |
tree | 0499601e433b21efbc49972fdbc94ef87a241529 | |
parent | 013ddd9e16c2ce369e86c08f5157d73834ca0af1 (diff) | |
parent | 537ca7b06eeae6a303d5677549460edc8f2b16b4 (diff) | |
download | python-barbicanclient-0254b25ab6d5d97320031f36d1383809a81e96f5.tar.gz |
Merge pull request #28 from jfwood/master
Add verifications entity logic to the client library.
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | barbicanclient/base.py | 2 | ||||
-rw-r--r-- | barbicanclient/client.py | 12 | ||||
-rw-r--r-- | barbicanclient/common/auth.py | 13 | ||||
-rw-r--r-- | barbicanclient/common/config.py | 45 | ||||
-rw-r--r-- | barbicanclient/common/utils.py | 10 | ||||
-rw-r--r-- | barbicanclient/keep.py | 93 | ||||
-rw-r--r-- | barbicanclient/orders.py | 19 | ||||
-rw-r--r-- | barbicanclient/secrets.py | 6 | ||||
-rw-r--r-- | barbicanclient/test/common/test_auth.py | 23 | ||||
-rw-r--r-- | barbicanclient/test/test_client.py | 119 | ||||
-rw-r--r-- | barbicanclient/test/test_client_orders.py | 131 | ||||
-rw-r--r-- | barbicanclient/test/test_client_secrets.py | 185 | ||||
-rw-r--r-- | barbicanclient/test/test_client_verifications.py | 135 | ||||
-rw-r--r-- | barbicanclient/verifications.py | 145 |
15 files changed, 848 insertions, 92 deletions
@@ -25,6 +25,8 @@ pip-log.txt .coverage .tox nosetests.xml +coverage.xml +flake8.log # pyenv .python-version diff --git a/barbicanclient/base.py b/barbicanclient/base.py index ca157a1..5e63f88 100644 --- a/barbicanclient/base.py +++ b/barbicanclient/base.py @@ -29,7 +29,7 @@ class BaseEntityManager(object): def total(self): """ - Returns the toatl number of entities stored in Barbican. + Returns the total number of entities stored in Barbican. """ href = '{0}/{1}'.format(self.api.base_url, self.entity) params = {'limit': 0, 'offset': 0} diff --git a/barbicanclient/client.py b/barbicanclient/client.py index 5888311..a03ec48 100644 --- a/barbicanclient/client.py +++ b/barbicanclient/client.py @@ -21,6 +21,7 @@ import requests from barbicanclient.openstack.common.gettextutils import _ from barbicanclient import orders from barbicanclient import secrets +from barbicanclient import verifications LOG = logging.getLogger(__name__) @@ -49,8 +50,8 @@ class HTTPAuthError(HTTPError): class Client(object): - def __init__(self, auth_plugin=None, endpoint=None, tenant_id=None, - insecure=False): + def __init__(self, session=None, auth_plugin=None, endpoint=None, + tenant_id=None, insecure=False): """ Barbican client object used to interact with barbican service. @@ -65,7 +66,7 @@ class Client(object): """ LOG.debug(_("Creating Client object")) - self._session = requests.Session() + self._session = session or requests.Session() self.verify = not insecure self.auth_plugin = auth_plugin @@ -98,6 +99,7 @@ class Client(object): self.base_url = '{0}/{1}'.format(self._barbican_url, self._tenant_id) self.secrets = secrets.SecretManager(self) self.orders = orders.OrderManager(self) + self.verifications = verifications.VerificationManager(self) def get(self, href, params=None): headers = {'Accept': 'application/json'} @@ -124,12 +126,12 @@ class Client(object): return resp.json() def _check_status_code(self, resp): - status = resp.status_code + status = resp.status_code if resp else None LOG.debug('Response status {0}'.format(status)) if status == 401: LOG.error('Auth error: {0}'.format(self._get_error_message(resp))) raise HTTPAuthError('{0}'.format(self._get_error_message(resp))) - if status >= 500: + if not status or status >= 500: LOG.error('5xx Server error: {0}'.format( self._get_error_message(resp) )) diff --git a/barbicanclient/common/auth.py b/barbicanclient/common/auth.py index 2c91c58..c663191 100644 --- a/barbicanclient/common/auth.py +++ b/barbicanclient/common/auth.py @@ -17,6 +17,7 @@ import logging from keystoneclient.v2_0 import client as ksclient from keystoneclient import exceptions + LOG = logging.getLogger(__name__) @@ -28,15 +29,15 @@ class AuthException(Exception): class KeystoneAuthV2(object): def __init__(self, auth_url='', username='', password='', - tenant_name='', tenant_id='', insecure=False): + tenant_name='', tenant_id='', insecure=False, keystone=None): if not all([auth_url, username, password, tenant_name or tenant_id]): raise ValueError('Please provide auth_url, username, password,' ' and tenant_id or tenant_name)') - self._keystone = ksclient.Client(username=username, - password=password, - tenant_name=tenant_name, - auth_url=auth_url, - insecure=insecure) + self._keystone = keystone or ksclient.Client(username=username, + password=password, + tenant_name=tenant_name, + auth_url=auth_url, + insecure=insecure) self._barbican_url = None #TODO(dmend): make these configurable self._service_type = 'keystore' diff --git a/barbicanclient/common/config.py b/barbicanclient/common/config.py index 865b748..4f68f99 100644 --- a/barbicanclient/common/config.py +++ b/barbicanclient/common/config.py @@ -46,48 +46,3 @@ def parse_args(args=None, usage=None, default_config_files=None): version=__version__, usage=usage, default_config_files=default_config_files) - - -def setup_logging(): - """ - Sets up the logging options - """ - - if CONF.log_config: - # Use a logging configuration file for all settings... - if os.path.exists(CONF.log_config): - logging.config.fileConfig(CONF.log_config) - return - else: - raise RuntimeError("Unable to locate specified logging " - "config file: %s" % CONF.log_config) - - root_logger = logging.root - if CONF.debug: - root_logger.setLevel(logging.DEBUG) - elif CONF.verbose: - root_logger.setLevel(logging.INFO) - else: - root_logger.setLevel(logging.WARNING) - - formatter = logging.Formatter(CONF.log_format, CONF.log_date_format) - - if CONF.use_syslog: - try: - facility = getattr(logging.handlers.SysLogHandler, - CONF.syslog_log_facility) - except AttributeError: - raise ValueError(_("Invalid syslog facility")) - - handler = logging.handlers.SysLogHandler(address='/dev/log', - facility=facility) - elif CONF.log_file: - logfile = CONF.log_file - if CONF.log_dir: - logfile = os.path.join(CONF.log_dir, logfile) - handler = logging.handlers.WatchedFileHandler(logfile) - else: - handler = logging.StreamHandler(sys.stdout) - - handler.setFormatter(formatter) - root_logger.addHandler(handler) diff --git a/barbicanclient/common/utils.py b/barbicanclient/common/utils.py deleted file mode 100644 index 55b1a0d..0000000 --- a/barbicanclient/common/utils.py +++ /dev/null @@ -1,10 +0,0 @@ -import urllib - - -def proc_template(template, **kwargs): - """ - Processes a templated URL by substituting the - dictionary args and returning the strings. - """ - return template.format(**dict([(k, urllib.quote(v)) - for k, v in kwargs.items()])) diff --git a/barbicanclient/keep.py b/barbicanclient/keep.py index eead191..f0e5f77 100644 --- a/barbicanclient/keep.py +++ b/barbicanclient/keep.py @@ -36,6 +36,7 @@ class Keep: self._add_store_args() self._add_get_args() self._add_list_args() + self._add_verify_args() self._add_delete_args() def _get_main_parser(self): @@ -44,9 +45,9 @@ class Keep: ) parser.add_argument('command', metavar='<entity>', - choices=['order', 'secret'], + choices=['order', 'secret', 'verification'], help='Entity used for command, e.g.,' - ' order, secret.') + ' order, secret, verification.') auth_group = parser.add_mutually_exclusive_group() auth_group.add_argument('--no-auth', '-N', action='store_true', help='Do not use authentication.') @@ -84,6 +85,27 @@ class Keep: 'option should be used with caution.') return parser + def _add_verify_args(self): + verify_parser = self.subparsers.add_parser('verify', + help='Create a new ' + 'verification.') + verify_parser.add_argument('--type', '-t', default='image', + help='resource type to verify, ' + 'such as "image".') + + verify_parser.add_argument('--ref', '-r', + help='reference URI to ' + 'resource to verify.') + + verify_parser.add_argument('--action', '-a', default='vm_attach', + help='action to perform on ' + 'resource, such as "vm_attach".') + + verify_parser.add_argument('--impersonation', '-i', default=True, + help='is impersonation allowed ' + 'for the resource.') + verify_parser.set_defaults(func=self.verify) + def _add_create_args(self): create_parser = self.subparsers.add_parser('create', help='Create a new order.') @@ -104,7 +126,8 @@ class Keep: default='application/octet-stream', help='the type/format of the secret to be' ' generated (default: %(default)s).') - create_parser.add_argument('--expiration', '-x', help='the expiration ' + create_parser.add_argument('--expiration', '-x', + help='the expiration ' 'time for the secret in ISO 8601 format.') create_parser.set_defaults(func=self.create) @@ -116,7 +139,8 @@ class Keep: store_parser.add_argument('--name', '-n', help='a human-friendly name.') store_parser.add_argument('--payload', '-p', help='the unencrypted' - ' secret; if provided, you must also provide' + ' secret; if provided, ' + 'you must also provide' ' a payload_content_type') store_parser.add_argument('--payload-content-type', '-t', help='the type/format of the provided ' @@ -127,7 +151,8 @@ class Keep: help='required if --payload-content-type is' ' "application/octet-stream".') store_parser.add_argument('--algorithm', '-a', default='aes', - help='the algorithm (default: %(default)s).') + help='the algorithm (default: ' + '%(default)s).') store_parser.add_argument('--bit-length', '-b', default=256, help='the bit length ' '(default: %(default)s).', @@ -142,19 +167,23 @@ class Keep: def _add_delete_args(self): delete_parser = self.subparsers.add_parser( 'delete', - help='Delete a secret or an order by providing its href.' + help='Delete a secret, order or ' + 'verification by providing its href.' ) delete_parser.add_argument('URI', help='The URI reference for the' - ' secret or order') + ' secret, order ' + 'or verification') delete_parser.set_defaults(func=self.delete) def _add_get_args(self): get_parser = self.subparsers.add_parser( 'get', - help='Retrieve a secret or an order by providing its URI.' + help='Retrieve a secret, order or ' + 'verification by providing its URI.' ) - get_parser.add_argument('URI', help='The URI reference for the secret' - ' or order.') + get_parser.add_argument('URI', help='The URI reference ' + 'for the secret, ' + 'order or verification.') get_parser.add_argument('--decrypt', '-d', help='if specified, keep' ' will retrieve the unencrypted secret data;' ' the data type can be specified with' @@ -170,9 +199,11 @@ class Keep: def _add_list_args(self): list_parser = self.subparsers.add_parser('list', - help='List secrets or orders') - list_parser.add_argument('--limit', '-l', default=10, help='specify t' - 'he limit to the number of items to list per' + help='List secrets, ' + 'orders or ' + 'verifications') + list_parser.add_argument('--limit', '-l', default=10, help='specify ' + 'the limit to the number of items to list per' ' page (default: %(default)s; maximum: 100)', type=int) list_parser.add_argument('--offset', '-o', default=0, help='specify t' @@ -211,8 +242,14 @@ class Keep: def delete(self, args): if args.command == 'secret': self.client.secrets.delete(args.URI) - else: + elif args.command == 'verification': + self.client.verifications.delete(args.URI) + elif args.command == 'order': self.client.orders.delete(args.URI) + else: + self.parser.exit(status=1, message='ERROR: delete is only ' + 'supported for secrets, ' + 'orders or verifications\n') def get(self, args): if args.command == 'secret': @@ -221,19 +258,43 @@ class Keep: args.payload_content_type) else: print self.client.secrets.get(args.URI) - else: + elif args.command == 'verification': + print self.client.verifications.get(args.URI) + elif args.command == 'order': print self.client.orders.get(args.URI) + else: + self.parser.exit(status=1, message='ERROR: get is only ' + 'supported for secrets, ' + 'orders or verifications\n') def list(self, args): if args.command == 'secret': ls = self.client.secrets.list(args.limit, args.offset) - else: + elif args.command == 'verification': + ls = self.client.verifications.list(args.limit, args.offset) + elif args.command == 'order': ls = self.client.orders.list(args.limit, args.offset) + else: + self.parser.exit(status=1, message='ERROR: get list is only ' + 'supported for secrets, ' + 'orders or verifications\n') for obj in ls: print obj print '{0}s displayed: {1} - offset: {2}'.format(args.command, len(ls), args.offset) + def verify(self, args): + if args.command == 'verification': + verify = self.client.verifications\ + .create(resource_type=args.type, + resource_ref=args.ref, + resource_action=args.action, + impersonation_allowed=args.impersonation) + print verify + else: + self.parser.exit(status=1, message='ERROR: verify is only ' + 'supported for verifications\n') + def execute(self, **kwargs): args = self.parser.parse_args(kwargs.get('argv')) if args.no_auth: diff --git a/barbicanclient/orders.py b/barbicanclient/orders.py index 3747306..7b2f3e3 100644 --- a/barbicanclient/orders.py +++ b/barbicanclient/orders.py @@ -29,6 +29,9 @@ class Order(object): Builds an order object from a dictionary. """ self.order_ref = order_dict['order_ref'] + + self.error_status_code = order_dict.get('error_status_code', None) + self.error_reason = order_dict.get('error_reason', None) self.status = order_dict.get('status') self.created = timeutils.parse_isotime(order_dict['created']) if order_dict.get('updated') is not None: @@ -38,13 +41,19 @@ class Order(object): self.secret_ref = order_dict.get('secret_ref') def __str__(self): - return ("Order - order href: {0}\n" + strg = ("Order - order href: {0}\n" " secret href: {1}\n" " created: {2}\n" " status: {3}\n" - .format(self.order_ref, self.secret_ref, - self.created, self.status) - ) + ).format(self.order_ref, self.secret_ref, + self.created, self.status) + + if self.error_status_code: + strg = ''.join([strg, (" error_status_code: {0}\n" + " error_reason: {1}\n" + ).format(self.error_status_code, + self.error_reason)]) + return strg def __repr__(self): return 'Order(order_ref={0})'.format(self.order_ref) @@ -121,7 +130,7 @@ class OrderManager(base.BaseEntityManager): :param offset: Offset orders to begin list :returns: list of Order objects """ - LOG.debug('Listing orders - offest {0} limit {1}'.format(offset, + LOG.debug('Listing orders - offset {0} limit {1}'.format(offset, limit)) href = '{0}/{1}'.format(self.api.base_url, self.entity) params = {'limit': limit, 'offset': offset} diff --git a/barbicanclient/secrets.py b/barbicanclient/secrets.py index a479f2c..a1e9b12 100644 --- a/barbicanclient/secrets.py +++ b/barbicanclient/secrets.py @@ -140,6 +140,12 @@ class SecretManager(base.BaseEntityManager): raise ValueError('secret_ref is required.') if not content_type: secret = self.get(secret_ref) + if secret.content_types is None: + raise ValueError('Secret has no encrypted data to decrypt.') + if 'default' not in secret.content_types: + raise ValueError("Must specify decrypt content-type as " + "secret does not specify a 'default' " + "content-type.") content_type = secret.content_types['default'] headers = {'Accept': content_type} return self.api.get_raw(secret_ref, headers) diff --git a/barbicanclient/test/common/test_auth.py b/barbicanclient/test/common/test_auth.py index 640301e..d31bcef 100644 --- a/barbicanclient/test/common/test_auth.py +++ b/barbicanclient/test/common/test_auth.py @@ -12,12 +12,35 @@ # implied. # See the License for the specific language governing permissions and # limitations under the License. + +import mock import unittest2 as unittest from barbicanclient.common import auth class WhenTestingKeystoneAuthentication(unittest.TestCase): + + def setUp(self): + self.keystone_client = mock.MagicMock() + + self.auth_url = 'https://www.yada.com' + self.username = 'user' + self.password = 'pw' + self.tenant_id = '1234' + + self.keystone_auth = auth.KeystoneAuthV2(auth_url=self.auth_url, + username=self.username, + password=self.password, + tenant_id=self.tenant_id, + keystone= + self.keystone_client) + def test_endpoint_username_password_tenant_are_required(self): with self.assertRaises(ValueError): keystone = auth.KeystoneAuthV2() + + def test_get_barbican_url(self): + barbican_url = 'https://www.barbican.com' + self.keystone_auth._barbican_url = barbican_url + self.assertEquals(barbican_url, self.keystone_auth.barbican_url) diff --git a/barbicanclient/test/test_client.py b/barbicanclient/test/test_client.py index c464568..8905264 100644 --- a/barbicanclient/test/test_client.py +++ b/barbicanclient/test/test_client.py @@ -13,13 +13,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -import json - import mock import unittest2 as unittest from barbicanclient import client -from barbicanclient.common import auth +from barbicanclient.openstack.common import timeutils +from barbicanclient.openstack.common import jsonutils class FakeAuth(object): @@ -30,7 +29,24 @@ class FakeAuth(object): self.tenant_id = tenant_id -class WhenTestingClient(unittest.TestCase): +class FakeResp(object): + def __init__(self, status_code, response_dict=None, content=None): + self.status_code = status_code + self.response_dict = response_dict + self.content = content + + def json(self): + if self.response_dict is None: + return None + resp = self.response_dict + resp['title'] = 'some title here' + return resp + + def content(self): + return self.content + + +class WhenTestingClientInit(unittest.TestCase): def setUp(self): self.auth_endpoint = 'https://localhost:5000/v2.0/' self.auth_token = 'fake_auth_token' @@ -91,3 +107,98 @@ class WhenTestingClient(unittest.TestCase): c = client.Client(auth_plugin=self.fake_auth) with self.assertRaises(client.HTTPClientError): c._check_status_code(resp) + + +class WhenTestingClientWithSession(unittest.TestCase): + def setUp(self): + self.endpoint = 'https://localhost:9311/v1/' + self.tenant_id = '1234567' + + self.entity = 'dummy-entity' + base = self.endpoint + self.tenant_id + "/" + self.entity_base = base + self.entity + "/" + self.entity_href = self.entity_base + '1234' + + self.entity_name = 'name' + self.entity_dict = {'name': self.entity_name} + + self.session = mock.MagicMock() + + self.client = client.Client(session=self.session, + endpoint=self.endpoint, + tenant_id=self.tenant_id) + + def test_should_post(self): + self.session.post.return_value = FakeResp(200, {'entity_ref': + self.entity_href}) + + resp_dict = self.client.post(self.entity, self.entity_dict) + + self.assertEqual(self.entity_href, resp_dict['entity_ref']) + + # Verify the correct URL was used to make the call. + args, kwargs = self.session.post.call_args + url = args[0] + self.assertEqual(self.entity_base, url) + + # Verify that correct information was sent in the call. + data = jsonutils.loads(kwargs['data']) + self.assertEqual(self.entity_name, data['name']) + + def test_should_get(self): + self.session.get.return_value = FakeResp(200, {'name': + self.entity_name}) + + resp_dict = self.client.get(self.entity_href) + + self.assertEqual(self.entity_name, resp_dict['name']) + + # Verify the correct URL was used to make the call. + args, kwargs = self.session.get.call_args + url = args[0] + self.assertEqual(self.entity_href, url) + + # Verify that correct information was sent in the call. + headers = kwargs['headers'] + self.assertEqual('application/json', headers['Accept']) + + def test_should_get_raw(self): + self.session.get.return_value = FakeResp(200, content='content') + + headers = {'Accept': 'application/octet-stream'} + content = self.client.get_raw(self.entity_href, headers) + + self.assertEqual('content', content) + + # Verify the correct URL was used to make the call. + args, kwargs = self.session.get.call_args + url = args[0] + self.assertEqual(self.entity_href, url) + + # Verify that correct information was sent in the call. + headers = kwargs['headers'] + self.assertEqual('application/octet-stream', headers['Accept']) + + def test_should_delete(self): + self.session.delete.return_value = FakeResp(200) + + self.client.delete(self.entity_href) + + # Verify the correct URL was used to make the call. + args, kwargs = self.session.delete.call_args + url = args[0] + self.assertEqual(self.entity_href, url) + + +class BaseEntityResource(unittest.TestCase): + def _setUp(self, entity): + self.endpoint = 'https://localhost:9311/v1/' + self.tenant_id = '1234567' + + self.entity = entity + base = self.endpoint + self.tenant_id + "/" + self.entity_base = base + self.entity + "/" + self.entity_href = self.entity_base + '1234' + + self.api = mock.MagicMock() + self.api.base_url = base[:-1] diff --git a/barbicanclient/test/test_client_orders.py b/barbicanclient/test/test_client_orders.py new file mode 100644 index 0000000..b6f1d99 --- /dev/null +++ b/barbicanclient/test/test_client_orders.py @@ -0,0 +1,131 @@ +# Copyright (c) 2013 Rackspace, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from barbicanclient import orders +from barbicanclient.openstack.common import timeutils +from barbicanclient.test import test_client +from barbicanclient.test import test_client_secrets as test_secrets + + +class OrderData(object): + def __init__(self): + self.created = str(timeutils.utcnow()) + + self.secret = test_secrets.SecretData() + self.status = 'ACTIVE' + self.order_dict = {'created': self.created, + 'status': self.status, + 'secret': self.secret.get_dict()} + + def get_dict(self, order_ref, secret_ref=None): + order = self.order_dict + order['order_ref'] = order_ref + if secret_ref: + order['secret_ref'] = secret_ref + return order + + +class WhenTestingOrders(test_client.BaseEntityResource): + + def setUp(self): + self._setUp('orders') + + self.order = OrderData() + + self.manager = orders.OrderManager(self.api) + + def test_should_entity_str(self): + order_obj = orders.Order(self.order.get_dict(self.entity_href)) + order_obj.error_status_code = '500' + order_obj.error_reason = 'Something is broken' + self.assertIn('status: ' + self.order.status, + str(order_obj)) + self.assertIn('error_status_code: 500', str(order_obj)) + + def test_should_entity_repr(self): + order_obj = orders.Order(self.order.get_dict(self.entity_href)) + self.assertIn('order_ref=' + self.entity_href, + repr(order_obj)) + + def test_should_create(self): + self.api.post.return_value = {'order_ref': self.entity_href} + + order_href = self.manager\ + .create(name=self.order.secret.name, + algorithm=self.order.secret.algorithm, + payload_content_type=self.order.secret.content) + + self.assertEqual(self.entity_href, order_href) + + # Verify the correct URL was used to make the call. + args, kwargs = self.api.post.call_args + entity_resp = args[0] + self.assertEqual(self.entity, entity_resp) + + # Verify that correct information was sent in the call. + order_req = args[1] + self.assertEqual(self.order.secret.name, order_req['secret']['name']) + self.assertEqual(self.order.secret.algorithm, + order_req['secret']['algorithm']) + self.assertEqual(self.order.secret.payload_content_type, + order_req['secret']['payload_content_type']) + + def test_should_get(self): + self.api.get.return_value = self.order.get_dict(self.entity_href) + + order = self.manager.get(order_ref=self.entity_href) + self.assertIsInstance(order, orders.Order) + self.assertEqual(self.entity_href, order.order_ref) + + # Verify the correct URL was used to make the call. + args, kwargs = self.api.get.call_args + url = args[0] + self.assertEqual(self.entity_href, url) + + def test_should_delete(self): + self.manager.delete(order_ref=self.entity_href) + + # Verify the correct URL was used to make the call. + args, kwargs = self.api.delete.call_args + url = args[0] + self.assertEqual(self.entity_href, url) + + def test_should_get_list(self): + order_resp = self.order.get_dict(self.entity_href) + self.api.get.return_value = {"orders": + [order_resp for v in xrange(3)]} + + orders_list = self.manager.list(limit=10, offset=5) + self.assertTrue(len(orders_list) == 3) + self.assertIsInstance(orders_list[0], orders.Order) + self.assertEqual(self.entity_href, orders_list[0].order_ref) + + # Verify the correct URL was used to make the call. + args, kwargs = self.api.get.call_args + url = args[0] + self.assertEqual(self.entity_base[:-1], url) + + # Verify that correct information was sent in the call. + params = args[1] + self.assertEqual(10, params['limit']) + self.assertEqual(5, params['offset']) + + def test_should_fail_get_no_href(self): + with self.assertRaises(ValueError): + self.manager.get(None) + + def test_should_fail_delete_no_href(self): + with self.assertRaises(ValueError): + self.manager.delete(None) diff --git a/barbicanclient/test/test_client_secrets.py b/barbicanclient/test/test_client_secrets.py new file mode 100644 index 0000000..2645314 --- /dev/null +++ b/barbicanclient/test/test_client_secrets.py @@ -0,0 +1,185 @@ +# Copyright (c) 2013 Rackspace, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from barbicanclient.test import test_client +from barbicanclient import secrets +from barbicanclient.openstack.common import timeutils + + +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.created = str(timeutils.utcnow()) + + self.secret_dict = {'name': self.name, + 'status': 'ACTIVE', + 'algorithm': self.algorithm, + 'created': self.created} + + def get_dict(self, secret_ref=None, content_types_dict=None): + secret = self.secret_dict + if secret_ref: + secret['secret_ref'] = secret_ref + if content_types_dict: + secret['content_types'] = content_types_dict + return secret + + +class WhenTestingSecrets(test_client.BaseEntityResource): + + def setUp(self): + self._setUp('secrets') + + self.secret = SecretData() + + self.manager = secrets.SecretManager(self.api) + + def test_should_entity_str(self): + secret_obj = secrets.Secret(self.secret.get_dict(self.entity_href)) + self.assertIn('name: ' + self.secret.name, + str(secret_obj)) + + def test_should_entity_repr(self): + secret_obj = secrets.Secret(self.secret.get_dict(self.entity_href)) + self.assertIn('name="{0}"'.format(self.secret.name), repr(secret_obj)) + + def test_should_store(self): + self.api.post.return_value = {'secret_ref': self.entity_href} + + secret_href = self.manager\ + .store(name=self.secret.name, + payload=self.secret.payload, + payload_content_type=self.secret.content) + + self.assertEqual(self.entity_href, secret_href) + + # Verify the correct URL was used to make the call. + args, kwargs = self.api.post.call_args + entity_resp = args[0] + self.assertEqual(self.entity, entity_resp) + + # Verify that correct information was sent in the call. + secret_req = args[1] + 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_get(self): + self.api.get.return_value = self.secret.get_dict(self.entity_href) + + secret = self.manager.get(secret_ref=self.entity_href) + self.assertIsInstance(secret, secrets.Secret) + self.assertEqual(self.entity_href, secret.secret_ref) + + # Verify the correct URL was used to make the call. + args, kwargs = self.api.get.call_args + url = args[0] + self.assertEqual(self.entity_href, url) + + def test_should_decrypt_with_content_type(self): + decrypted = 'decrypted text here' + self.api.get_raw.return_value = decrypted + + secret = self.manager.decrypt(secret_ref=self.entity_href, + content_type='application/octet-stream') + self.assertEqual(decrypted, secret) + + # Verify the correct URL was used to make the call. + args, kwargs = self.api.get_raw.call_args + url = args[0] + self.assertEqual(self.entity_href, url) + + # Verify that correct information was sent in the call. + headers = args[1] + self.assertEqual('application/octet-stream', headers['Accept']) + + def test_should_decrypt_without_content_type(self): + content_types_dict = {'default': 'application/octet-stream'} + self.api.get.return_value = self.secret.get_dict(self.entity_href, + content_types_dict) + decrypted = 'decrypted text here' + self.api.get_raw.return_value = decrypted + + secret = self.manager.decrypt(secret_ref=self.entity_href) + self.assertEqual(decrypted, secret) + + # Verify the correct URL was used to make the call. + args, kwargs = self.api.get.call_args + url = args[0] + self.assertEqual(self.entity_href, url) + + # Verify the correct URL was used to make the call. + args, kwargs = self.api.get_raw.call_args + url = args[0] + self.assertEqual(self.entity_href, url) + + # Verify that correct information was sent in the call. + headers = args[1] + self.assertEqual('application/octet-stream', headers['Accept']) + + def test_should_delete(self): + self.manager.delete(secret_ref=self.entity_href) + + # Verify the correct URL was used to make the call. + args, kwargs = self.api.delete.call_args + url = args[0] + self.assertEqual(self.entity_href, url) + + def test_should_get_list(self): + secret_resp = self.secret.get_dict(self.entity_href) + self.api.get.return_value = {"secrets": + [secret_resp for v in xrange(3)]} + + secrets_list = self.manager.list(limit=10, offset=5) + self.assertTrue(len(secrets_list) == 3) + self.assertIsInstance(secrets_list[0], secrets.Secret) + self.assertEqual(self.entity_href, secrets_list[0].secret_ref) + + # Verify the correct URL was used to make the call. + args, kwargs = self.api.get.call_args + url = args[0] + self.assertEqual(self.entity_base[:-1], url) + + # Verify that correct information was sent in the call. + params = args[1] + self.assertEqual(10, params['limit']) + self.assertEqual(5, params['offset']) + + def test_should_fail_get_no_href(self): + with self.assertRaises(ValueError): + self.manager.get(None) + + def test_should_fail_decrypt_no_content_types(self): + self.api.get.return_value = self.secret.get_dict(self.entity_href) + + with self.assertRaises(ValueError): + self.manager.decrypt(secret_ref=self.entity_href) + + def test_should_fail_decrypt_no_default_content_type(self): + content_types_dict = {'no-default': 'application/octet-stream'} + self.api.get.return_value = self.secret.get_dict(self.entity_href, + content_types_dict) + + with self.assertRaises(ValueError): + self.manager.decrypt(secret_ref=self.entity_href) + + def test_should_fail_delete_no_href(self): + with self.assertRaises(ValueError): + self.manager.delete(None) diff --git a/barbicanclient/test/test_client_verifications.py b/barbicanclient/test/test_client_verifications.py new file mode 100644 index 0000000..d7f3b8f --- /dev/null +++ b/barbicanclient/test/test_client_verifications.py @@ -0,0 +1,135 @@ +# Copyright (c) 2013 Rackspace, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from barbicanclient import verifications as vers +from barbicanclient.openstack.common import timeutils +from barbicanclient.test import test_client + + +class VerificationData(object): + def __init__(self): + self.created = str(timeutils.utcnow()) + + self.resource_type = 'image' + self.resource_ref = 'http://www.image.com/v1/images/1234' + self.resource_action = 'vm_attach' + self.impersonation_allowed = True + + self.verification_dict = {'created': self.created, + 'resource_type': self.resource_type, + 'resource_ref': self.resource_ref, + 'resource_action': self.resource_action, + 'impersonation_allowed': + self.impersonation_allowed} + + def get_dict(self, verification_ref): + verify = self.verification_dict + verify['verification_ref'] = verification_ref + return verify + + +class WhenTestingVerifications(test_client.BaseEntityResource): + + def setUp(self): + self._setUp('verifications') + + self.verify = VerificationData() + + self.manager = vers.VerificationManager(self.api) + + def test_should_entity_str(self): + verif_obj = vers.Verification(self.verify.get_dict(self.entity_href)) + verif_obj.error_status_code = '500' + verif_obj.error_reason = 'Something is broken' + self.assertIn('resource_type: ' + self.verify.resource_type, + str(verif_obj)) + self.assertIn('error_status_code: 500', str(verif_obj)) + + def test_should_entity_repr(self): + verif = vers.Verification(self.verify.get_dict(self.entity_href)) + self.assertIn('verification_ref=' + self.entity_href, + repr(verif)) + + def test_should_create(self): + self.api.post.return_value = {'verification_ref': self.entity_href} + + order_href = self.manager\ + .create(resource_type=self.verify.resource_type, + resource_ref=self.verify.resource_ref, + resource_action=self.verify.resource_action) + + self.assertEqual(self.entity_href, order_href) + + # Verify the correct URL was used to make the call. + args, kwargs = self.api.post.call_args + entity_resp = args[0] + self.assertEqual(self.entity, entity_resp) + + # Verify that correct information was sent in the call. + verify_req = args[1] + self.assertEqual(self.verify.resource_type, + verify_req['resource_type']) + self.assertEqual(self.verify.resource_action, + verify_req['resource_action']) + self.assertEqual(self.verify.resource_ref, + verify_req['resource_ref']) + + def test_should_get(self): + self.api.get.return_value = self.verify.get_dict(self.entity_href) + + verify = self.manager.get(verification_ref=self.entity_href) + self.assertIsInstance(verify, vers.Verification) + self.assertEqual(self.entity_href, verify.verif_ref) + + # Verify the correct URL was used to make the call. + args, kwargs = self.api.get.call_args + url = args[0] + self.assertEqual(self.entity_href, url) + + def test_should_delete(self): + self.manager.delete(verification_ref=self.entity_href) + + # Verify the correct URL was used to make the call. + args, kwargs = self.api.delete.call_args + url = args[0] + self.assertEqual(self.entity_href, url) + + def test_should_get_list(self): + verify_resp = self.verify.get_dict(self.entity_href) + self.api.get.return_value = {"verifications": + [verify_resp for v in xrange(3)]} + + verifies = self.manager.list(limit=10, offset=5) + self.assertTrue(len(verifies) == 3) + self.assertIsInstance(verifies[0], vers.Verification) + self.assertEqual(self.entity_href, verifies[0].verif_ref) + + # Verify the correct URL was used to make the call. + args, kwargs = self.api.get.call_args + url = args[0] + self.assertEqual(self.entity_base[:-1], url) + + # Verify that correct information was sent in the call. + params = args[1] + self.assertEqual(10, params['limit']) + self.assertEqual(5, params['offset']) + + def test_should_fail_get_no_href(self): + with self.assertRaises(ValueError): + self.manager.get(None) + + def test_should_fail_delete_no_href(self): + with self.assertRaises(ValueError): + self.manager.delete(None) diff --git a/barbicanclient/verifications.py b/barbicanclient/verifications.py new file mode 100644 index 0000000..f5e9225 --- /dev/null +++ b/barbicanclient/verifications.py @@ -0,0 +1,145 @@ +# Copyright (c) 2013 Rackspace, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from barbicanclient import base +from barbicanclient.openstack.common.gettextutils import _ +from barbicanclient.openstack.common import log as logging +from barbicanclient.openstack.common import timeutils + + +LOG = logging.getLogger(__name__) + + +class Verification(object): + + def __init__(self, verif_dict): + """ + Builds a verification object from a dictionary. + """ + self.verif_ref = verif_dict['verification_ref'] + self.resource_type = verif_dict['resource_type'] + self.resource_ref = verif_dict['resource_ref'] + self.resource_action = verif_dict['resource_action'] + self.impersonation_allowed = verif_dict['impersonation_allowed'] + self.is_verified = verif_dict.get('is_verified', False) + + self.error_status_code = verif_dict.get('error_status_code', None) + self.error_reason = verif_dict.get('error_reason', None) + self.status = verif_dict.get('status') + self.created = timeutils.parse_isotime(verif_dict['created']) + if verif_dict.get('updated') is not None: + self.updated = timeutils.parse_isotime(verif_dict['updated']) + else: + self.updated = None + + def __str__(self): + strg = ("Verification - verification href: {0}\n" + " resource_type: {1}\n" + " resource_ref: {2}\n" + " resource_action: {3}\n" + " impersonation_allowed: {4}\n" + " is_verified: {5}\n" + " created: {6}\n" + " status: {7}\n" + ).format(self.verif_ref, + self.resource_type, + self.resource_ref, + self.resource_action, + self.impersonation_allowed, + self.is_verified, + self.created, + self.status) + + if self.error_status_code: + strg = ''.join([strg, (" error_status_code: {0}\n" + " error_reason: {1}\n" + ).format(self.error_status_code, + self.error_reason)]) + return strg + + def __repr__(self): + return 'Verification(verification_ref={0})'.format(self.verif_ref) + + +class VerificationManager(base.BaseEntityManager): + + def __init__(self, api): + super(VerificationManager, self).__init__(api, 'verifications') + + def create(self, + resource_type=None, + resource_ref=None, + resource_action=None, + impersonation_allowed=False): + """ + Creates a new Verification in Barbican + + :param resource_type: Type of resource to verify + :param resource_ref: Reference to resource + :param resource_action: Action to be performed on or with the resource + :param impersonation_allowed: True if users/projects interacting + : with resource can be impersonated + :returns: Verification href for the created verification + """ + LOG.debug(_("Creating verification")) + + verif_dict = {'resource_type': resource_type, + 'resource_ref': resource_ref, + 'resource_action': resource_action, + 'impersonation_allowed': impersonation_allowed} + self._remove_empty_keys(verif_dict) + + LOG.debug(_("Request body: {0}").format(verif_dict)) + + resp = self.api.post(self.entity, verif_dict) + return resp['verification_ref'] + + def get(self, verification_ref): + """ + Returns a verification object + + :param verification_ref: The href for the verification instance + """ + LOG.debug(_("Getting verification - " + "Verification href: {0}").format(verification_ref)) + if not verification_ref: + raise ValueError('verif_ref is required.') + resp = self.api.get(verification_ref) + return Verification(resp) + + def delete(self, verification_ref): + """ + Deletes a verification + + :param verification_ref: The href for the verification instance + """ + if not verification_ref: + raise ValueError('verif_ref is required.') + self.api.delete(verification_ref) + + def list(self, limit=10, offset=0): + """ + Lists all verifications for the tenant + + :param limit: Max number of verifications returned + :param offset: Offset verifications to begin list + :returns: list of Verification objects + """ + LOG.debug('Listing verifications - ' + 'offset {0} limit {1}'.format(offset, limit)) + href = '{0}/{1}'.format(self.api.base_url, self.entity) + params = {'limit': limit, 'offset': offset} + resp = self.api.get(href, params) + + return [Verification(o) for o in resp['verifications']] |