# Copyright (c) 2013-2014 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.

"""
This test module focuses on typical-flow business logic tests with the API
resource classes. For RBAC tests of these classes, see the
'resources_policy_test.py' module.
"""
import mimetypes

import mock
import pecan
from testtools import testcase
import webtest

from barbican import api
from barbican.api import app
from barbican.api import controllers
from barbican.common import exception as excep
from barbican.common import hrefs
from barbican.common import utils as barbican_utils
import barbican.context
from barbican.model import models
from barbican.tests import utils


def get_barbican_env(external_project_id):
    """Create and return a barbican.context for use with the RBAC decorator

    Injects the provided external_project_id.
    """
    kwargs = {'roles': None,
              'user_id': None,
              'project_id': external_project_id,
              'is_admin': True}
    ctx = barbican.context.RequestContext(**kwargs)
    ctx.policy_enforcer = None
    barbican_env = {'barbican.context': ctx}
    return barbican_env


def create_secret(id_ref="id", name="name",
                  algorithm=None, bit_length=None,
                  mode=None, encrypted_datum=None, content_type=None):
    """Generate a Secret entity instance."""
    info = {'id': id_ref,
            'name': name,
            'algorithm': algorithm,
            'bit_length': bit_length,
            'mode': mode}
    secret = models.Secret(info)
    secret.id = id_ref
    if encrypted_datum:
        secret.encrypted_data = [encrypted_datum]
    if content_type:
        content_meta = models.SecretStoreMetadatum('content_type',
                                                   content_type)
        secret.secret_store_metadata['content_type'] = content_meta
    return secret


def create_order_with_meta(id_ref="id", order_type="certificate", meta={},
                           status='PENDING'):
    """Generate an Order entity instance with Metadata."""
    order = models.Order()
    order.id = id_ref
    order.type = order_type
    order.meta = meta
    order.status = status
    return order


def validate_datum(test, datum):
    test.assertIsNone(datum.kek_meta_extended)
    test.assertIsNotNone(datum.kek_meta_project)
    test.assertTrue(datum.kek_meta_project.bind_completed)
    test.assertIsNotNone(datum.kek_meta_project.plugin_name)
    test.assertIsNotNone(datum.kek_meta_project.kek_label)


def create_container(id_ref, project_id=None, external_project_id=None):
    """Generate a Container entity instance."""
    container = models.Container()
    container.id = id_ref
    container.name = 'test name'
    container.type = 'rsa'
    container_secret = models.ContainerSecret()
    container_secret.container_id = id_ref
    container_secret.secret_id = '123'
    container.container_secrets.append(container_secret)

    if project_id:
        project = models.Project()
        project.id = project_id
        project.external_id = external_project_id
        container.project = project
    return container


def create_consumer(container_id, project_id, id_ref):
    """Generate a ContainerConsumerMetadatum entity instance."""
    data = {
        'name': 'test name',
        'URL': 'http://test/url'
    }
    consumer = models.ContainerConsumerMetadatum(container_id,
                                                 project_id,
                                                 data)
    consumer.id = id_ref
    return consumer


class SecretAllowAllMimeTypesDecoratorTest(utils.BaseTestCase):

    def setUp(self):
        super(SecretAllowAllMimeTypesDecoratorTest, self).setUp()
        self.mimetype_values = set(mimetypes.types_map.values())

    @pecan.expose(generic=True)
    @barbican_utils.allow_all_content_types
    def _empty_pecan_exposed_function(self):
        pass

    def _empty_function(self):
        pass

    def test_mimetypes_successfully_added_to_mocked_function(self):
        empty_function = mock.MagicMock()
        empty_function._pecan = {}
        func = barbican_utils.allow_all_content_types(empty_function)
        cfg = func._pecan
        self.assertEqual(len(self.mimetype_values), len(cfg['content_types']))

    def test_mimetypes_successfully_added_to_pecan_exposed_function(self):
        cfg = self._empty_pecan_exposed_function._pecan
        self.assertEqual(len(self.mimetype_values), len(cfg['content_types']))

    def test_decorator_raises_if_function_not_pecan_exposed(self):
        self.assertRaises(AttributeError,
                          barbican_utils.allow_all_content_types,
                          self._empty_function)


class FunctionalTest(utils.BaseTestCase, utils.MockModelRepositoryMixin,
                     testcase.WithAttributes):

    def setUp(self):
        super(FunctionalTest, self).setUp()
        root = self.root
        config = {'app': {'root': root}}
        pecan.set_config(config, overwrite=True)
        self.app = webtest.TestApp(pecan.make_app(root))

    def tearDown(self):
        super(FunctionalTest, self).tearDown()
        pecan.set_config({}, overwrite=True)

    @property
    def root(self):
        return controllers.versions.VersionController()


class BaseSecretsResource(FunctionalTest):
    """Base test class for the Secrets resource."""

    def setUp(self):
        super(BaseSecretsResource, self).setUp()
        self.app = webtest.TestApp(app.build_wsgi_app(self.root))
        self.app.extra_environ = get_barbican_env(self.external_project_id)

    @property
    def root(self):
        self._init()

        class RootController(object):
            secrets = controllers.secrets.SecretsController()

        return RootController()

    def _init(self, payload=b'not-encrypted',
              payload_content_type='text/plain',
              payload_content_encoding=None):
        self.name = 'name'
        self.payload = payload
        self.payload_content_type = payload_content_type
        self.payload_content_encoding = payload_content_encoding
        self.secret_algorithm = 'AES'
        self.secret_bit_length = 256
        self.secret_mode = 'CBC'
        self.secret_req = {'name': self.name,
                           'algorithm': self.secret_algorithm,
                           'bit_length': self.secret_bit_length,
                           'creator_id': None,
                           'mode': self.secret_mode}
        if payload:
            self.secret_req['payload'] = payload
        if payload_content_type:
            self.secret_req['payload_content_type'] = payload_content_type
        if payload_content_encoding:
            self.secret_req['payload_content_encoding'] = (
                payload_content_encoding)

        # Set up mocked project
        self.external_project_id = 'keystone1234'
        self.project_entity_id = 'tid1234'
        self.project = models.Project()
        self.project.id = self.project_entity_id
        self.project.external_id = self.external_project_id

        # Set up mocked project repo
        self.project_repo = mock.MagicMock()
        self.project_repo.find_by_external_project_id.return_value = (
            self.project)
        self.setup_project_repository_mock(self.project_repo)

        # Set up mocked secret
        self.secret = models.Secret()
        self.secret.id = utils.generate_test_valid_uuid()

        # Set up mocked secret repo
        self.secret_repo = mock.MagicMock()
        self.secret_repo.create_from.return_value = self.secret
        self.setup_secret_repository_mock(self.secret_repo)

        # Set up mocked encrypted datum repo
        self.datum_repo = mock.MagicMock()
        self.datum_repo.create_from.return_value = None
        self.setup_encrypted_datum_repository_mock(self.datum_repo)

        # Set up mocked kek datum
        self.kek_datum = models.KEKDatum()
        self.kek_datum.kek_label = "kek_label"
        self.kek_datum.bind_completed = False
        self.kek_datum.algorithm = ''
        self.kek_datum.bit_length = 0
        self.kek_datum.mode = ''
        self.kek_datum.plugin_meta = ''

        # Set up mocked kek datum repo
        self.kek_repo = mock.MagicMock()
        self.kek_repo.find_or_create_kek_datum.return_value = self.kek_datum
        self.setup_kek_datum_repository_mock(self.kek_repo)

        # Set up mocked secret meta repo
        self.setup_secret_meta_repository_mock()

        # Set up mocked transport key
        self.transport_key = models.TransportKey(
            'default_plugin_name', 'XXXABCDEF')
        self.transport_key_id = 'tkey12345'
        self.tkey_url = hrefs.convert_transport_key_to_href(
            self.transport_key.id)

        # Set up mocked transport key
        self.setup_transport_key_repository_mock()


class WhenGettingPuttingOrDeletingSecretUsingSecretResource(FunctionalTest):
    def setUp(self):
        super(
            WhenGettingPuttingOrDeletingSecretUsingSecretResource, self
        ).setUp()
        self.app = webtest.TestApp(app.build_wsgi_app(self.root))
        self.app.extra_environ = get_barbican_env(self.external_project_id)

    @property
    def root(self):
        self._init()

        class RootController(object):
            secrets = controllers.secrets.SecretsController()

        return RootController()

    def _init(self):
        self.project_id = 'projectid1234'
        self.external_project_id = 'keystone1234'
        self.name = 'name1234'

        secret_id = utils.generate_test_valid_uuid()
        datum_id = "iddatum1"
        kek_id = "idkek1"

        self.secret_algorithm = "AES"
        self.secret_bit_length = 256
        self.secret_mode = "CBC"

        self.kek_project = models.KEKDatum()
        self.kek_project.id = kek_id
        self.kek_project.active = True
        self.kek_project.bind_completed = False
        self.kek_project.kek_label = "kek_label"

        self.datum = models.EncryptedDatum()
        self.datum.id = datum_id
        self.datum.secret_id = secret_id
        self.datum.kek_id = kek_id
        self.datum.kek_meta_project = self.kek_project
        self.datum.content_type = "text/plain"
        self.datum.cypher_text = "aaaa"  # base64 value.

        self.secret = create_secret(id_ref=secret_id,
                                    name=self.name,
                                    algorithm=self.secret_algorithm,
                                    bit_length=self.secret_bit_length,
                                    mode=self.secret_mode,
                                    encrypted_datum=self.datum,
                                    content_type=self.datum.content_type)

        self.secret.secret_acls = []
        self.secret.project = mock.MagicMock()
        self.secret.project.external_id = self.external_project_id

        # Set up mocked project
        self.project = models.Project()
        self.project.id = self.project_id
        self.project.external_id = self.external_project_id

        # Set up mocked project repo
        self.project_repo = mock.MagicMock()
        self.project_repo.get.return_value = self.project
        self.project_repo.find_by_external_project_id.return_value = (
            self.project)
        self.setup_project_repository_mock(self.project_repo)

        # Set up mocked secret repo
        self.secret_repo = mock.Mock()
        self.secret_repo.get = mock.Mock(return_value=self.secret)
        self.secret_repo.get_secret_by_id = mock.Mock(return_value=self.secret)
        self.secret_repo.delete_entity_by_id = mock.Mock(return_value=None)
        self.setup_secret_repository_mock(self.secret_repo)

        # Set up mocked encrypted datum repo
        self.datum_repo = mock.MagicMock()
        self.datum_repo.create_from.return_value = None
        self.setup_encrypted_datum_repository_mock(self.datum_repo)

        # Set up mocked kek datum repo
        self.setup_kek_datum_repository_mock()

        # Set up mocked secret meta repo
        self.secret_meta_repo = mock.MagicMock()
        self.secret_meta_repo.get_metadata_for_secret.return_value = None
        self.setup_secret_meta_repository_mock(self.secret_meta_repo)

        # Set up mocked transport key
        self.transport_key_model = models.TransportKey(
            "default_plugin", "my transport key")

        # Set up mocked transport key repo
        self.transport_key_repo = mock.MagicMock()
        self.transport_key_repo.get.return_value = self.transport_key_model
        self.setup_transport_key_repository_mock(self.transport_key_repo)

        self.transport_key_id = 'tkey12345'

    @mock.patch('barbican.plugin.resources.get_transport_key_id_for_retrieval')
    def test_should_get_secret_as_json(self, mock_get_transport_key):
        mock_get_transport_key.return_value = None
        resp = self.app.get(
            '/secrets/{0}/'.format(self.secret.id),
            headers={'Accept': 'application/json', 'Accept-Encoding': 'gzip'}
        )
        self.secret_repo.get_secret_by_id.assert_called_once_with(
            entity_id=self.secret.id,
            suppress_exception=True)
        self.assertEqual(200, resp.status_int)

        self.assertNotIn('content_encodings', resp.namespace)
        self.assertIn('content_types', resp.namespace)
        self.assertIn(self.datum.content_type,
                      resp.namespace['content_types'].values())
        self.assertNotIn('mime_type', resp.namespace)

    @testcase.attr('deprecated')
    @mock.patch('barbican.plugin.resources.get_secret')
    def test_should_get_secret_as_plain_based_on_content_type(self,
                                                              mock_get_secret):
        data = 'unencrypted_data'
        mock_get_secret.return_value = data

        resp = self.app.get(
            '/secrets/{0}/payload/'.format(self.secret.id),
            headers={'Accept': 'text/plain'}
        )

        self.secret_repo.get_secret_by_id.assert_called_once_with(
            entity_id=self.secret.id,
            suppress_exception=True)
        self.assertEqual(200, resp.status_int)

        self.assertEqual(data, resp.body.decode())
        mock_get_secret.assert_called_once_with(
            'text/plain',
            self.secret,
            self.project,
            None,
            None
        )

    @mock.patch('barbican.plugin.resources.get_secret')
    def test_should_get_secret_as_plain_with_twsk(self, mock_get_secret):
        data = 'encrypted_data'
        mock_get_secret.return_value = data

        twsk = "trans_wrapped_session_key"
        resp = self.app.get(
            ('/secrets/{0}/payload/'
             '?trans_wrapped_session_key={1}&transport_key_id={2}')
            .format(self.secret.id, twsk, self.transport_key_id),
            headers={'Accept': 'text/plain'}
        )

        self.secret_repo.get_secret_by_id.assert_called_once_with(
            entity_id=self.secret.id,
            suppress_exception=True)
        self.assertEqual(200, resp.status_int)

        self.assertEqual(data, resp.body.decode())
        mock_get_secret.assert_called_once_with(
            'text/plain',
            self.secret,
            self.project,
            twsk,
            self.transport_key_model.transport_key
        )

    @testcase.attr('deprecated')
    @mock.patch('barbican.plugin.resources.get_secret')
    def test_should_get_secret_as_plain_with_twsk_based_on_content_type(
            self, mock_get_secret):
        data = 'encrypted_data'
        mock_get_secret.return_value = data

        twsk = "trans_wrapped_session_key"
        resp = self.app.get(
            ('/secrets/{0}/'
             '?trans_wrapped_session_key={1}&transport_key_id={2}')
            .format(self.secret.id, twsk, self.transport_key_id),
            headers={'Accept': 'text/plain'}
        )

        self.secret_repo.get_secret_by_id.assert_called_once_with(
            entity_id=self.secret.id,
            suppress_exception=True)
        self.assertEqual(200, resp.status_int)

        self.assertEqual(data, resp.body.decode())
        mock_get_secret.assert_called_once_with(
            'text/plain',
            self.secret,
            self.project,
            twsk,
            self.transport_key_model.transport_key
        )

    @mock.patch('barbican.plugin.resources.get_secret')
    def test_should_throw_exception_for_get_when_twsk_but_no_tkey_id(
            self, mock_get_secret):
        data = 'encrypted_data'
        mock_get_secret.return_value = data

        twsk = "trans_wrapped_session_key"
        resp = self.app.get(
            '/secrets/{0}/payload/?trans_wrapped_session_key={1}'.format(
                self.secret.id, twsk),
            headers={'Accept': 'text/plain'},
            expect_errors=True
        )

        self.secret_repo.get_secret_by_id.assert_called_once_with(
            entity_id=self.secret.id,
            suppress_exception=True)
        self.assertEqual(400, resp.status_int)

    @testcase.attr('deprecated')
    @mock.patch('barbican.plugin.resources.get_secret')
    def test_should_throw_exception_for_get_when_twsk_but_no_tkey_id_old_way(
            self, mock_get_secret):
        data = 'encrypted_data'
        mock_get_secret.return_value = data

        twsk = "trans_wrapped_session_key"
        resp = self.app.get(
            '/secrets/{0}/payload/?trans_wrapped_session_key={1}'.format(
                self.secret.id, twsk),
            headers={'Accept': 'text/plain'},
            expect_errors=True
        )

        self.secret_repo.get_secret_by_id.assert_called_once_with(
            entity_id=self.secret.id,
            suppress_exception=True)
        self.assertEqual(400, resp.status_int)

    @mock.patch('barbican.plugin.resources.get_transport_key_id_for_retrieval')
    def test_should_get_secret_meta_for_binary(self, mock_get_transport_key):
        mock_get_transport_key.return_value = None
        self.datum.content_type = "application/octet-stream"
        self.secret.secret_store_metadata['content_type'].value = (
            self.datum.content_type
        )
        self.datum.cypher_text = 'aaaa'

        resp = self.app.get(
            '/secrets/{0}/'.format(self.secret.id),
            headers={'Accept': 'application/json', 'Accept-Encoding': 'gzip'}
        )

        self.secret_repo.get_secret_by_id.assert_called_once_with(
            entity_id=self.secret.id,
            suppress_exception=True)

        self.assertEqual(200, resp.status_int)

        self.assertIsNotNone(resp.namespace)
        self.assertIn('content_types', resp.namespace)
        self.assertIn(self.datum.content_type,
                      resp.namespace['content_types'].values())

    @mock.patch('barbican.plugin.resources.get_transport_key_id_for_retrieval')
    def test_should_get_secret_meta_for_binary_with_tkey(
            self, mock_get_transport_key_id):
        mock_get_transport_key_id.return_value = self.transport_key_id
        self.datum.content_type = "application/octet-stream"
        self.secret.secret_store_metadata['content_type'].value = (
            self.datum.content_type
        )
        self.datum.cypher_text = 'aaaa'

        resp = self.app.get(
            '/secrets/{0}/?transport_key_needed=true'.format(
                self.secret.id),
            headers={'Accept': 'application/json', 'Accept-Encoding': 'gzip'}
        )

        self.secret_repo.get_secret_by_id.assert_called_once_with(
            entity_id=self.secret.id,
            suppress_exception=True)

        self.assertEqual(200, resp.status_int)

        self.assertIsNotNone(resp.namespace)
        self.assertIn('content_types', resp.namespace)
        self.assertIn(self.datum.content_type,
                      resp.namespace['content_types'].values())
        self.assertIn('transport_key_ref', resp.namespace)
        self.assertEqual(
            hrefs.convert_transport_key_to_href(self.transport_key_id),
            resp.namespace['transport_key_ref']
        )

    @testcase.attr('deprecated')
    @mock.patch('barbican.plugin.resources.get_secret')
    def test_should_get_secret_as_binary_based_on_content_type(
            self, mock_get_secret):
        data = 'unencrypted_data'
        mock_get_secret.return_value = data

        self.datum.content_type = "application/octet-stream"
        self.datum.cypher_text = 'aaaa'

        resp = self.app.get(
            '/secrets/{0}/'.format(self.secret.id),
            headers={
                'Accept': 'application/octet-stream',
                'Accept-Encoding': 'gzip'
            }
        )

        self.assertEqual(data, resp.body.decode())

        mock_get_secret.assert_called_once_with(
            'application/octet-stream',
            self.secret,
            self.project,
            None,
            None
        )

    @mock.patch('barbican.plugin.resources.store_secret')
    def test_should_put_secret_as_plain_with_tkey_id(self, mock_store_secret):
        self.secret.encrypted_data = []
        self.secret.secret_store_metadata = {}

        resp = self.app.put(
            '/secrets/{0}/?transport_key_id={1}'.format(
                self.secret.id, self.transport_key_id),
            'plain text',
            headers={'Accept': 'text/plain', 'Content-Type': 'text/plain'},
        )

        self.assertEqual(204, resp.status_int)

        mock_store_secret.assert_called_once_with(
            unencrypted_raw=b'plain text',
            content_type_raw='text/plain',
            content_encoding=None,
            secret_model=self.secret,
            project_model=self.project,
            transport_key_id=self.transport_key_id
        )

    @mock.patch('barbican.plugin.resources.store_secret')
    def test_should_put_secret_as_binary_with_tkey_id(self, mock_store_secret):
        self.secret.encrypted_data = []
        self.secret.secret_store_metadata = {}

        resp = self.app.put(
            '/secrets/{0}/?transport_key_id={1}'.format(
                self.secret.id, self.transport_key_id),
            'plain text',
            headers={
                'Accept': 'text/plain',
                'Content-Type': 'application/octet-stream'
            },
        )

        self.assertEqual(204, resp.status_int)

        mock_store_secret.assert_called_once_with(
            unencrypted_raw=b'plain text',
            content_type_raw='application/octet-stream',
            content_encoding=None,
            secret_model=self.secret,
            project_model=self.project,
            transport_key_id=self.transport_key_id
        )


class WhenAddingNavigationHrefs(utils.BaseTestCase):

    def setUp(self):
        super(WhenAddingNavigationHrefs, self).setUp()

        self.resource_name = 'orders'
        self.external_project_id = '12345'
        self.num_elements = 100
        self.data = {}

    def test_add_nav_hrefs_adds_next_only(self):
        offset = 0
        limit = 10

        data_with_hrefs = hrefs.add_nav_hrefs(
            self.resource_name, offset, limit, self.num_elements, self.data)

        self.assertNotIn('previous', data_with_hrefs)
        self.assertIn('next', data_with_hrefs)

    def test_add_nav_hrefs_adds_both_next_and_previous(self):
        offset = 10
        limit = 10

        data_with_hrefs = hrefs.add_nav_hrefs(
            self.resource_name, offset, limit, self.num_elements, self.data)

        self.assertIn('previous', data_with_hrefs)
        self.assertIn('next', data_with_hrefs)

    def test_add_nav_hrefs_adds_previous_only(self):
        offset = 90
        limit = 10

        data_with_hrefs = hrefs.add_nav_hrefs(
            self.resource_name, offset, limit, self.num_elements, self.data)

        self.assertIn('previous', data_with_hrefs)
        self.assertNotIn('next', data_with_hrefs)


class TestingJsonSanitization(utils.BaseTestCase):

    def test_json_sanitization_without_array(self):
        json_without_array = {"name": "name", "algorithm": "AES",
                              "payload_content_type": "  text/plain   ",
                              "mode": "CBC", "bit_length": 256,
                              "payload": "not-encrypted"}

        self.assertTrue(json_without_array['payload_content_type']
                        .startswith(' '), "whitespace should be there")
        self.assertTrue(json_without_array['payload_content_type']
                        .endswith(' '), "whitespace should be there")
        api.strip_whitespace(json_without_array)
        self.assertFalse(json_without_array['payload_content_type']
                         .startswith(' '), "whitespace should be gone")
        self.assertFalse(json_without_array['payload_content_type']
                         .endswith(' '), "whitespace should be gone")

    def test_json_sanitization_with_array(self):
        json_with_array = {"name": "name", "algorithm": "AES",
                           "payload_content_type": "text/plain",
                           "mode": "CBC", "bit_length": 256,
                           "payload": "not-encrypted",
                           "an-array":
                           [{"name": " item 1"},
                            {"name": "item2 "}]}

        self.assertTrue(json_with_array['an-array'][0]['name']
                        .startswith(' '), "whitespace should be there")
        self.assertTrue(json_with_array['an-array'][1]['name']
                        .endswith(' '), "whitespace should be there")
        api.strip_whitespace(json_with_array)
        self.assertFalse(json_with_array['an-array'][0]['name']
                         .startswith(' '), "whitespace should be gone")
        self.assertFalse(json_with_array['an-array'][1]['name']
                         .endswith(' '), "whitespace should be gone")


class WhenCreatingConsumersUsingConsumersResource(FunctionalTest):
    def setUp(self):
        super(
            WhenCreatingConsumersUsingConsumersResource, self
        ).setUp()
        self.app = webtest.TestApp(app.build_wsgi_app(self.root))
        self.app.extra_environ = get_barbican_env(self.external_project_id)

    @property
    def root(self):
        self._init()

        class RootController(object):
            containers = controllers.containers.ContainersController()

        return RootController()

    def _init(self):
        self.name = 'test container name'
        self.type = 'generic'
        self.secret_refs = [
            {
                'name': 'test secret 1',
                'secret_ref': '1231'
            },
            {
                'name': 'test secret 2',
                'secret_ref': '1232'
            },
            {
                'name': 'test secret 3',
                'secret_ref': '1233'
            }
        ]

        self.consumer_ref = {
            'name': 'test_consumer1',
            'URL': 'http://consumer/1'
        }

        self.project_internal_id = 'projectid1234'
        self.external_project_id = 'keystoneid1234'

        # Set up mocked project
        self.project = models.Project()
        self.project.id = self.project_internal_id
        self.project.external_id = self.external_project_id

        # Set up mocked project repo
        self.project_repo = mock.MagicMock()
        self.project_repo.get.return_value = self.project
        self.setup_project_repository_mock(self.project_repo)

        # Set up mocked quota enforcer
        self.quota_patch = mock.patch(
            'barbican.common.quota.QuotaEnforcer.enforce', return_value=None)
        self.quota_patch.start()
        self.addCleanup(self.quota_patch.stop)

        # Set up mocked container
        self.container = create_container(
            id_ref=utils.generate_test_valid_uuid(),
            project_id=self.project_internal_id,
            external_project_id=self.external_project_id)

        # Set up mocked container repo
        self.container_repo = mock.MagicMock()
        self.container_repo.get_container_by_id.return_value = self.container
        self.setup_container_repository_mock(self.container_repo)

        # Set up secret repo
        self.secret_repo = mock.MagicMock()
        self.secret_repo.create_from.return_value = None
        self.setup_secret_repository_mock(self.secret_repo)

        # Set up container consumer repo
        self.consumer_repo = mock.MagicMock()
        self.consumer_repo.create_from.return_value = None
        self.setup_container_consumer_repository_mock(self.consumer_repo)

        self.container_req = {'name': self.name,
                              'type': self.type,
                              'secret_refs': self.secret_refs}

    def test_should_add_new_consumer(self):
        resp = self.app.post_json(
            '/containers/{0}/consumers/'.format(self.container.id),
            self.consumer_ref
        )
        self.assertEqual(200, resp.status_int)
        self.assertNotIn(self.external_project_id, resp.headers['Location'])

        args, kwargs = self.consumer_repo.create_or_update_from.call_args
        consumer = args[0]
        self.assertIsInstance(consumer, models.ContainerConsumerMetadatum)

    def test_should_fail_consumer_bad_json(self):
        resp = self.app.post(
            '/containers/{0}/consumers/'.format(self.container.id),
            '',
            expect_errors=True
        )
        self.assertEqual(415, resp.status_int)

    def test_should_404_when_container_ref_doesnt_exist(self):
        self.container_repo.get_container_by_id.return_value = None
        resp = self.app.post_json(
            '/containers/{0}/consumers/'.format('bad_id'),
            self.consumer_ref, expect_errors=True
        )
        self.assertEqual(404, resp.status_int)


class WhenGettingOrDeletingConsumersUsingConsumerResource(FunctionalTest):

    def setUp(self):
        super(
            WhenGettingOrDeletingConsumersUsingConsumerResource, self
        ).setUp()
        self.app = webtest.TestApp(app.build_wsgi_app(self.root))
        self.app.extra_environ = get_barbican_env(self.external_project_id)

    @property
    def root(self):
        self._init()

        class RootController(object):
            containers = controllers.containers.ContainersController()

        return RootController()

    def _init(self):
        self.external_project_id = 'keystoneid1234'
        self.project_internal_id = 'projectid1234'

        # Set up mocked project
        self.project = models.Project()
        self.project.id = self.project_internal_id
        self.project.external_id = self.external_project_id

        # Set up mocked project repo
        self.project_repo = mock.MagicMock()
        self.project_repo.get.return_value = self.project
        self.setup_project_repository_mock(self.project_repo)

        # Set up mocked container
        self.container = create_container(
            id_ref=utils.generate_test_valid_uuid(),
            project_id=self.project_internal_id,
            external_project_id=self.external_project_id)

        # Set up mocked consumers
        self.consumer = create_consumer(
            self.container.id, self.project_internal_id,
            id_ref=utils.generate_test_valid_uuid())
        self.consumer2 = create_consumer(
            self.container.id, self.project_internal_id,
            id_ref=utils.generate_test_valid_uuid())

        self.consumer_ref = {
            'name': self.consumer.name,
            'URL': self.consumer.URL
        }

        # Set up mocked container repo
        self.container_repo = mock.MagicMock()
        self.container_repo.get_container_by_id.return_value = self.container
        self.setup_container_repository_mock(self.container_repo)

        # Set up mocked container consumer repo
        self.consumer_repo = mock.MagicMock()
        self.consumer_repo.get_by_values.return_value = self.consumer
        self.consumer_repo.delete_entity_by_id.return_value = None
        self.setup_container_consumer_repository_mock(self.consumer_repo)

        # Set up mocked secret repo
        self.setup_secret_repository_mock()

    def test_should_get_consumer(self):
        ret_val = ([self.consumer], 0, 0, 1)
        self.consumer_repo.get_by_container_id.return_value = ret_val

        resp = self.app.get('/containers/{0}/consumers/'.format(
            self.container.id
        ))
        self.assertEqual(200, resp.status_int)

        self.consumer_repo.get_by_container_id.assert_called_once_with(
            self.container.id,
            limit_arg=None,
            offset_arg=0,
            suppress_exception=True
        )

        self.assertEqual(self.consumer.name, resp.json['consumers'][0]['name'])
        self.assertEqual(self.consumer.URL, resp.json['consumers'][0]['URL'])

    def test_should_404_when_container_ref_doesnt_exist(self):
        self.container_repo.get_container_by_id.return_value = None
        resp = self.app.get('/containers/{0}/consumers/'.format(
            'bad_id'
        ), expect_errors=True)
        self.assertEqual(404, resp.status_int)

    def test_should_get_consumer_by_id(self):
        self.consumer_repo.get.return_value = self.consumer
        resp = self.app.get('/containers/{0}/consumers/{1}/'.format(
            self.container.id, self.consumer.id
        ))
        self.assertEqual(200, resp.status_int)

    def test_should_404_with_bad_consumer_id(self):
        self.consumer_repo.get.return_value = None
        resp = self.app.get('/containers/{0}/consumers/{1}/'.format(
            self.container.id, 'bad_id'
        ), expect_errors=True)
        self.assertEqual(404, resp.status_int)

    def test_should_get_no_consumers(self):
        self.consumer_repo.get_by_container_id.return_value = ([], 0, 0, 0)
        resp = self.app.get('/containers/{0}/consumers/'.format(
            self.container.id
        ))
        self.assertEqual(200, resp.status_int)

    def test_should_delete_consumer(self):
        self.app.delete_json('/containers/{0}/consumers/'.format(
            self.container.id
        ), self.consumer_ref)

        self.consumer_repo.delete_entity_by_id.assert_called_once_with(
            self.consumer.id, self.external_project_id)

    def test_should_fail_deleting_consumer_bad_json(self):
        resp = self.app.delete(
            '/containers/{0}/consumers/'.format(self.container.id),
            '',
            expect_errors=True
        )
        self.assertEqual(415, resp.status_int)

    def test_should_404_on_delete_when_consumer_not_found(self):
        old_return = self.consumer_repo.get_by_values.return_value
        self.consumer_repo.get_by_values.return_value = None
        resp = self.app.delete_json('/containers/{0}/consumers/'.format(
            self.container.id
        ), self.consumer_ref, expect_errors=True)
        self.consumer_repo.get_by_values.return_value = old_return
        self.assertEqual(404, resp.status_int)
        # Error response should have json content type
        self.assertEqual("application/json", resp.content_type)

    def test_should_404_on_delete_when_consumer_not_found_later(self):
        self.consumer_repo.delete_entity_by_id.side_effect = excep.NotFound()
        resp = self.app.delete_json('/containers/{0}/consumers/'.format(
            self.container.id
        ), self.consumer_ref, expect_errors=True)
        self.consumer_repo.delete_entity_by_id.side_effect = None
        self.assertEqual(404, resp.status_int)
        # Error response should have json content type
        self.assertEqual("application/json", resp.content_type)

    def test_should_delete_consumers_on_container_delete(self):
        consumers = [self.consumer, self.consumer2]
        ret_val = (consumers, 0, 0, 1)
        self.consumer_repo.get_by_container_id.return_value = ret_val

        resp = self.app.delete(
            '/containers/{0}/'.format(self.container.id)
        )
        self.assertEqual(204, resp.status_int)

        # Verify consumers were deleted
        calls = []
        for consumer in consumers:
            calls.append(mock.call(consumer.id, self.external_project_id))
        self.consumer_repo.delete_entity_by_id.assert_has_calls(
            calls, any_order=True
        )

    def test_should_pass_on_container_delete_with_missing_consumers(self):
        consumers = [self.consumer, self.consumer2]
        ret_val = (consumers, 0, 0, 1)
        self.consumer_repo.get_by_container_id.return_value = ret_val
        self.consumer_repo.delete_entity_by_id.side_effect = excep.NotFound

        resp = self.app.delete(
            '/containers/{0}/'.format(self.container.id)
        )
        self.assertEqual(204, resp.status_int)

        # Verify consumers were deleted
        calls = []
        for consumer in consumers:
            calls.append(mock.call(consumer.id, self.external_project_id))
        self.consumer_repo.delete_entity_by_id.assert_has_calls(
            calls, any_order=True
        )


class WhenPerformingUnallowedOperationsOnConsumers(FunctionalTest):
    def setUp(self):
        super(
            WhenPerformingUnallowedOperationsOnConsumers, self
        ).setUp()
        self.app = webtest.TestApp(app.build_wsgi_app(self.root))
        self.app.extra_environ = get_barbican_env(self.external_project_id)

    @property
    def root(self):
        self._init()

        class RootController(object):
            containers = controllers.containers.ContainersController()

        return RootController()

    def _init(self):
        self.name = 'test container name'
        self.type = 'generic'
        self.secret_refs = [
            {
                'name': 'test secret 1',
                'secret_ref': '1231'
            },
            {
                'name': 'test secret 2',
                'secret_ref': '1232'
            },
            {
                'name': 'test secret 3',
                'secret_ref': '1233'
            }
        ]

        self.consumer_ref = {
            'name': 'test_consumer1',
            'URL': 'http://consumer/1'
        }
        self.external_project_id = 'keystoneid1234'
        self.project_internal_id = 'projectid1234'

        # Set up mocked project
        self.project = models.Project()
        self.project.id = self.project_internal_id
        self.project.external_id = self.external_project_id

        # Set up mocked project repo
        self.project_repo = mock.MagicMock()
        self.project_repo.get.return_value = self.project
        self.setup_project_repository_mock(self.project_repo)

        # Set up mocked container
        self.container = create_container(
            id_ref=utils.generate_test_valid_uuid(),
            project_id=self.project_internal_id,
            external_project_id=self.external_project_id)

        # Set up mocked container consumers
        self.consumer = create_consumer(
            self.container.id, self.project_internal_id,
            id_ref=utils.generate_test_valid_uuid())
        self.consumer2 = create_consumer(
            self.container.id, self.project_internal_id,
            id_ref=utils.generate_test_valid_uuid())

        self.consumer_ref = {
            'name': self.consumer.name,
            'URL': self.consumer.URL
        }

        # Set up container repo
        self.container_repo = mock.MagicMock()
        self.container_repo.get_container_by_id.return_value = self.container
        self.setup_container_repository_mock(self.container_repo)

        # Set up container consumer repo
        self.consumer_repo = mock.MagicMock()
        self.consumer_repo.get_by_values.return_value = self.consumer
        self.consumer_repo.delete_entity_by_id.return_value = None
        self.setup_container_consumer_repository_mock(self.consumer_repo)

        # Set up secret repo
        self.setup_secret_repository_mock()

    def test_should_not_allow_put_on_consumers(self):
        ret_val = ([self.consumer], 0, 0, 1)
        self.consumer_repo.get_by_container_id.return_value = ret_val

        resp = self.app.put_json(
            '/containers/{0}/consumers/'.format(self.container.id),
            self.consumer_ref,
            expect_errors=True
        )
        self.assertEqual(405, resp.status_int)

    def test_should_not_allow_post_on_consumer_by_id(self):
        self.consumer_repo.get.return_value = self.consumer
        resp = self.app.post_json(
            '/containers/{0}/consumers/{1}/'.format(self.container.id,
                                                    self.consumer.id),
            self.consumer_ref,
            expect_errors=True
        )
        self.assertEqual(405, resp.status_int)

    def test_should_not_allow_put_on_consumer_by_id(self):
        self.consumer_repo.get.return_value = self.consumer
        resp = self.app.put_json(
            '/containers/{0}/consumers/{1}/'.format(self.container.id,
                                                    self.consumer.id),
            self.consumer_ref,
            expect_errors=True
        )
        self.assertEqual(405, resp.status_int)

    def test_should_not_allow_delete_on_consumer_by_id(self):
        self.consumer_repo.get.return_value = self.consumer
        resp = self.app.delete(
            '/containers/{0}/consumers/{1}/'.format(self.container.id,
                                                    self.consumer.id),
            expect_errors=True
        )
        self.assertEqual(405, resp.status_int)


class WhenOwnershipMismatch(FunctionalTest):

    def setUp(self):
        super(
            WhenOwnershipMismatch, self
        ).setUp()
        self.app = webtest.TestApp(app.build_wsgi_app(self.root))
        self.app.extra_environ = get_barbican_env(self.external_project_id)

    @property
    def root(self):
        self._init()

        class RootController(object):
            containers = controllers.containers.ContainersController()
        return RootController()

    def _init(self):
        self.external_project_id = 'keystoneid1234'
        self.project_internal_id = 'projectid1234'

        # Set up mocked project
        self.project = models.Project()
        self.project.id = self.project_internal_id
        self.project.external_id = self.external_project_id

        # Set up mocked project repo
        self.project_repo = mock.MagicMock()
        self.project_repo.get.return_value = self.project
        self.setup_project_repository_mock(self.project_repo)

        # Set up mocked container
        self.container = create_container(
            id_ref=utils.generate_test_valid_uuid(),
            project_id=self.project_internal_id,
            external_project_id='differentProjectId')

        # Set up mocked consumers
        self.consumer = create_consumer(self.container.id,
                                        self.project_internal_id,
                                        id_ref='id2')
        self.consumer2 = create_consumer(self.container.id,
                                         self.project_internal_id,
                                         id_ref='id3')

        self.consumer_ref = {
            'name': self.consumer.name,
            'URL': self.consumer.URL
        }

        # Set up mocked container repo
        self.container_repo = mock.MagicMock()
        self.container_repo.get.return_value = self.container
        self.container_repo.get_container_by_id.return_value = self.container
        self.setup_container_repository_mock(self.container_repo)

        # Set up mocked container consumer repo
        self.consumer_repo = mock.MagicMock()
        self.consumer_repo.get_by_values.return_value = self.consumer
        self.consumer_repo.delete_entity_by_id.return_value = None
        self.setup_container_consumer_repository_mock(self.consumer_repo)

        # Set up mocked secret repo
        self.setup_secret_repository_mock()

    def test_consumer_check_ownership_mismatch(self):
        resp = self.app.delete_json(
            '/containers/{0}/consumers/'.format(self.container.id),
            self.consumer_ref, expect_errors=True)
        self.assertEqual(403, resp.status_int)
