# -*- coding: utf-8 -*-
#  Copyright (c) 2015 OpenStack Foundation
#
# 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.
import base64
import mock
import unittest
import sys
sys.modules['castellan'] = mock.Mock()
sys.modules['castellan.common'] = mock.Mock()
sys.modules['castellan.common.credentials'] = mock.Mock()

from keystoneauth1.exceptions.connection import ConnectFailure
from keystoneauth1.exceptions.http import Unauthorized
from keystoneclient.exceptions import DiscoveryFailure
from swift.common.middleware.crypto import kms_keymaster
from swift.common.swob import Request
from test.unit.common.middleware.helpers import FakeSwift, FakeAppThatExcepts

TEST_KMS_INVALID_KEY_ID = 'invalid-kms-key-id'
TEST_KMS_NONEXISTENT_KEY_ID = '11111111-1111-1111-1111-ffffffffffff'
TEST_KMS_OPAQUE_KEY_ID = '22222222-2222-2222-2222-aaaaaaaaaaaa'
TEST_KMS_SHORT_KEY_ID = '22222222-2222-2222-2222-bbbbbbbbbbbb'
TEST_KMS_DES_KEY_ID = '22222222-2222-2222-2222-cccccccccccc'
TEST_KMS_NONE_KEY_ID = '22222222-2222-2222-2222-dddddddddddd'
TEST_KMS_INVALID_API_VERSION = 'vBadVersion'
TEST_KMS_INVALID_USER_DOMAIN_NAME = "baduserdomainname"
TEST_KMS_CONNECT_FAILURE_URL = 'http://endpoint_url_connect_error:45621'
TEST_KMS_NON_BARBICAN_URL = 'http://endpoint_url_nonbarbican:45621'
TEST_PROXYSERVER_CONF_EXTERNAL_KEYMASTER_CONF = {
    'keymaster_config_path': 'PATH_TO_KEYMASTER_CONFIG_FILE',
}
TEST_KMS_KEYMASTER_CONF = {
    'auth_endpoint': 'kmsauthurlv3',
    'password': 'kmspass',
    'username': 'kmsuser',
    'user_domain_id': None,
    'user_domain_name': 'default',
    'project_id': None,
    'project_name': 'kmsproject',
    'project_domain_id': None,
    'project_domain_name': 'default',
    'key_id': 'valid_kms_key_id-abcdefg-123456'
}


def capture_start_response():
    calls = []

    def start_response(*args):
        calls.append(args)
    return start_response, calls


def mock_castellan_api_side_effect(*args, **kwargs):
    return MockBarbicanKeyManager(args[0])


def mock_options_set_defaults_side_effect(*args, **kwargs):
    '''
    Add options from kwargs into args dict.
    '''
    args[0].update(kwargs)


def mock_config_opts_side_effect(*args, **kwargs):
    return dict()


def mock_keystone_password_side_effect(auth_url, username, password,
                                       project_name, user_domain_name,
                                       project_domain_name, user_id,
                                       user_domain_id, trust_id,
                                       domain_id, domain_name, project_id,
                                       project_domain_id, reauthenticate):
    return MockPassword(auth_url, username, password, project_name,
                        user_domain_name, project_domain_name, user_id,
                        user_domain_id, trust_id, domain_id, domain_name,
                        project_id, project_domain_id, reauthenticate)


ERR_MESSAGE_SECRET_INCORRECTLY_SPECIFIED = 'Secret incorrectly specified.'
ERR_MESSAGE_KEY_UUID_NOT_FOUND = 'Key not found, uuid: '


class MockBarbicanKeyManager(object):
    def __init__(self, conf):
        self.conf = conf

    def get(self, ctxt, key_id):
        # If authentication fails, raise an exception here.
        if (TEST_KMS_KEYMASTER_CONF['username'] !=
                ctxt.username
                or TEST_KMS_KEYMASTER_CONF['password'] !=
                ctxt.password or
                TEST_KMS_KEYMASTER_CONF['user_domain_name'] !=
                ctxt.user_domain_name):
            raise Unauthorized(
                message='The request you have made requires authentication.',
                http_status=401)
        elif self.conf['auth_endpoint'] == TEST_KMS_CONNECT_FAILURE_URL:
            raise ConnectFailure('Unable to establish connection')
        elif self.conf['auth_endpoint'] == TEST_KMS_NON_BARBICAN_URL:
            raise DiscoveryFailure(
                'Could not determine a suitable URL for the plugin')
        elif (self.conf['auth_endpoint'] !=
              TEST_KMS_KEYMASTER_CONF['auth_endpoint']):
            raise Unauthorized(
                message='Cannot authorize API client.')
        elif (key_id == TEST_KMS_NONEXISTENT_KEY_ID):
            message = ERR_MESSAGE_KEY_UUID_NOT_FOUND + key_id
            '''
            Raising a ManagedObjectNotFoundError would require importing it
            from castellan.common.exception. To avoid this import, raising a
            general Exception.
            '''
            raise Exception(message)
        elif key_id == TEST_KMS_INVALID_KEY_ID:
            raise ValueError(ERR_MESSAGE_SECRET_INCORRECTLY_SPECIFIED)
        elif key_id == TEST_KMS_NONE_KEY_ID:
            return None
        if 'unicode' in key_id:
            key_str = key_id[0] * 32
        else:
            key_str = (str(key_id[0]) * 32).encode('utf8')
        return MockBarbicanKey(key_str, key_id)


class MockBarbicanKey(object):
    def __init__(self, key_material, key_id):
        self.key_material = key_material
        self.bit_length = len(key_material) * 8
        if key_id == TEST_KMS_OPAQUE_KEY_ID:
            self.format = 'Opaque'
        else:
            self.format = 'RAW'
            self.algorithm = "aes"
        if key_id == TEST_KMS_DES_KEY_ID:
            self.format = 'des'
        if key_id == TEST_KMS_SHORT_KEY_ID:
            self.bit_length = 128
            self.key_material[:128]

    def get_encoded(self):
        return self.key_material

    def format(self):
        return self.format


class MockPassword(object):
    def __init__(self, auth_url, username, password, project_name,
                 user_domain_name, project_domain_name, user_id,
                 user_domain_id, trust_id, domain_id, domain_name, project_id,
                 project_domain_id, reauthenticate):
        self.auth_url = auth_url
        self.password = password
        self.username = username
        self.user_domain_name = user_domain_name
        self.project_name = project_name
        self.project_domain_name = project_domain_name
        self.user_id = user_id,
        self.user_domain_id = user_domain_id,
        self.trust_id = trust_id,
        self.domain_id = domain_id,
        self.domain_name = domain_name,
        self.project_id = project_id,
        self.project_domain_id = project_domain_id,
        self.reauthenticate = reauthenticate


class TestKmsKeymaster(unittest.TestCase):
    """
    Unit tests for storing the encryption root secret in a Barbican external
    key management system accessed using Castellan.
    """

    def setUp(self):
        super(TestKmsKeymaster, self).setUp()
        self.swift = FakeSwift()

    """
    Tests using the v3 Identity API, where all calls to Barbican are mocked.
    """

    @mock.patch('swift.common.middleware.crypto.keymaster.readconf')
    @mock.patch.object(kms_keymaster.KmsKeyMaster,
                       '_get_root_secret')
    def test_filter_v3(self, mock_get_root_secret_from_kms,
                       mock_readconf):
        mock_get_root_secret_from_kms.return_value = (
            base64.b64encode(b'x' * 32))
        mock_readconf.return_value = TEST_KMS_KEYMASTER_CONF
        factory = kms_keymaster.filter_factory(TEST_KMS_KEYMASTER_CONF)
        self.assertTrue(callable(factory))
        self.assertTrue(callable(factory(self.swift)))

    @mock.patch('swift.common.middleware.crypto.keymaster.readconf')
    @mock.patch.object(kms_keymaster.KmsKeyMaster,
                       '_get_root_secret')
    def test_app_exception_v3(self, mock_get_root_secret_from_kms,
                              mock_readconf):
        mock_get_root_secret_from_kms.return_value = (
            base64.b64encode(b'x' * 32))
        mock_readconf.return_value = TEST_KMS_KEYMASTER_CONF
        app = kms_keymaster.KmsKeyMaster(
            FakeAppThatExcepts(), TEST_KMS_KEYMASTER_CONF)
        req = Request.blank('/', environ={'REQUEST_METHOD': 'PUT'})
        start_response, _ = capture_start_response()
        self.assertRaises(Exception, app, req.environ, start_response)

    @mock.patch.object(kms_keymaster.KmsKeyMaster, '_get_root_secret')
    def test_get_root_secret(
            self, mock_get_root_secret_from_kms):
        # Successful call with coarse _get_root_secret_from_kms() mock.
        mock_get_root_secret_from_kms.return_value = (
            base64.b64encode(b'x' * 32))
        # Provide valid Barbican configuration parameters in proxy-server
        # config.
        self.app = kms_keymaster.KmsKeyMaster(self.swift,
                                              TEST_KMS_KEYMASTER_CONF)
        # Verify that _get_root_secret_from_kms() was called with the
        # correct parameters.
        mock_get_root_secret_from_kms.assert_called_with(
            TEST_KMS_KEYMASTER_CONF
        )

    @mock.patch('swift.common.middleware.crypto.keymaster.readconf')
    @mock.patch.object(kms_keymaster.KmsKeyMaster, '_get_root_secret')
    def test_get_root_secret_from_external_file(
            self, mock_get_root_secret_from_kms, mock_readconf):
        # Return valid Barbican configuration parameters.
        mock_readconf.return_value = TEST_KMS_KEYMASTER_CONF
        # Successful call with coarse _get_root_secret_from_kms() mock.
        mock_get_root_secret_from_kms.return_value = (
            base64.b64encode(b'x' * 32))
        # Point to external config in proxy-server config.
        self.app = kms_keymaster.KmsKeyMaster(
            self.swift, TEST_PROXYSERVER_CONF_EXTERNAL_KEYMASTER_CONF)
        # Verify that _get_root_secret_from_kms() was called with the
        # correct parameters.
        mock_get_root_secret_from_kms.assert_called_with(
            TEST_KMS_KEYMASTER_CONF
        )
        self.assertEqual(mock_readconf.mock_calls, [
            mock.call('PATH_TO_KEYMASTER_CONFIG_FILE', 'kms_keymaster')])

    @mock.patch('swift.common.middleware.crypto.kms_keymaster.'
                'keystone_password.KeystonePassword')
    @mock.patch('swift.common.middleware.crypto.kms_keymaster.cfg')
    @mock.patch('swift.common.middleware.crypto.kms_keymaster.options')
    @mock.patch('swift.common.middleware.crypto.keymaster.readconf')
    @mock.patch('swift.common.middleware.crypto.kms_keymaster.key_manager')
    def test_mocked_castellan_keymanager(
            self, mock_castellan_key_manager, mock_readconf,
            mock_castellan_options, mock_oslo_config, mock_keystone_password):
        # Successful call with finer grained mocks.
        mock_keystone_password.side_effect = (
            mock_keystone_password_side_effect)
        '''
        Set side_effect functions.
        '''
        mock_castellan_key_manager.API.side_effect = (
            mock_castellan_api_side_effect)
        mock_castellan_options.set_defaults.side_effect = (
            mock_options_set_defaults_side_effect)
        mock_oslo_config.ConfigOpts.side_effect = (
            mock_config_opts_side_effect)
        '''
        Return valid Barbican configuration parameters.
        '''
        mock_readconf.return_value = TEST_KMS_KEYMASTER_CONF

        '''
        Verify that no exceptions are raised by the mocked functions.
        '''
        try:
            self.app = kms_keymaster.KmsKeyMaster(self.swift,
                                                  TEST_KMS_KEYMASTER_CONF)
        except Exception:
            print("Unexpected error: %s" % sys.exc_info()[0])
            raise

    @mock.patch('swift.common.middleware.crypto.kms_keymaster.'
                'keystone_password.KeystonePassword')
    @mock.patch('swift.common.middleware.crypto.kms_keymaster.cfg')
    @mock.patch('swift.common.middleware.crypto.kms_keymaster.options')
    @mock.patch('swift.common.middleware.crypto.keymaster.readconf')
    @mock.patch('swift.common.middleware.crypto.kms_keymaster.key_manager')
    def test_mocked_castellan_keymanager_invalid_key_id(
            self, mock_castellan_key_manager, mock_readconf,
            mock_castellan_options, mock_oslo_config,
            mock_keystone_password):
        # Invalid key ID.
        mock_keystone_password.side_effect = (
            mock_keystone_password_side_effect)
        '''
        Set side_effect functions.
        '''
        mock_castellan_key_manager.API.side_effect = (
            mock_castellan_api_side_effect)
        mock_castellan_options.set_defaults.side_effect = (
            mock_options_set_defaults_side_effect)
        mock_oslo_config.ConfigOpts.side_effect = (
            mock_config_opts_side_effect)
        '''
        Return invalid Barbican configuration parameters.
        '''
        kms_conf = dict(TEST_KMS_KEYMASTER_CONF)
        kms_conf['key_id'] = TEST_KMS_INVALID_KEY_ID
        mock_readconf.return_value = kms_conf

        '''
        Verify that an exception is raised by the mocked function.
        '''
        try:
            self.app = kms_keymaster.KmsKeyMaster(
                self.swift, TEST_PROXYSERVER_CONF_EXTERNAL_KEYMASTER_CONF)
            raise Exception('Success even though key id invalid')
        except ValueError as e:
            self.assertEqual(e.args[0],
                             ERR_MESSAGE_SECRET_INCORRECTLY_SPECIFIED)
        except Exception:
            print("Unexpected error: %s" % sys.exc_info()[0])
            raise

    @mock.patch('swift.common.middleware.crypto.kms_keymaster.'
                'keystone_password.KeystonePassword')
    @mock.patch('swift.common.middleware.crypto.kms_keymaster.cfg')
    @mock.patch('swift.common.middleware.crypto.kms_keymaster.options')
    @mock.patch('swift.common.middleware.crypto.keymaster.readconf')
    @mock.patch('swift.common.middleware.crypto.kms_keymaster.key_manager')
    def test_mocked_castellan_keymanager_nonexistent_key_id(
            self, mock_castellan_key_manager, mock_readconf,
            mock_castellan_options, mock_oslo_config,
            mock_keystone_password):
        # Nonexistent key.
        mock_keystone_password.side_effect = (
            mock_keystone_password_side_effect)
        '''
        Set side_effect functions.
        '''
        mock_castellan_key_manager.API.side_effect = (
            mock_castellan_api_side_effect)
        mock_castellan_options.set_defaults.side_effect = (
            mock_options_set_defaults_side_effect)
        mock_oslo_config.ConfigOpts.side_effect = (
            mock_config_opts_side_effect)
        '''
        Return invalid Barbican configuration parameters.
        '''
        kms_conf = dict(TEST_KMS_KEYMASTER_CONF)
        kms_conf['key_id'] = TEST_KMS_NONEXISTENT_KEY_ID
        mock_readconf.return_value = kms_conf

        '''
        Verify that an exception is raised by the mocked function.
        '''
        try:
            self.app = kms_keymaster.KmsKeyMaster(
                self.swift, TEST_PROXYSERVER_CONF_EXTERNAL_KEYMASTER_CONF)
            raise Exception('Success even though key id invalid')
        except Exception as e:
            expected_message = ('Key not found, uuid: ' +
                                TEST_KMS_NONEXISTENT_KEY_ID)
            self.assertEqual(e.args[0], expected_message)

    @mock.patch('swift.common.middleware.crypto.kms_keymaster.'
                'keystone_password.KeystonePassword')
    @mock.patch('swift.common.middleware.crypto.kms_keymaster.cfg')
    @mock.patch('swift.common.middleware.crypto.kms_keymaster.options')
    @mock.patch('swift.common.middleware.crypto.keymaster.readconf')
    @mock.patch('swift.common.middleware.crypto.kms_keymaster.key_manager')
    def test_mocked_castellan_keymanager_invalid_key_format(
            self, mock_castellan_key_manager, mock_readconf,
            mock_castellan_options, mock_oslo_config,
            mock_keystone_password):
        # Nonexistent key.
        mock_keystone_password.side_effect = (
            mock_keystone_password_side_effect)
        '''
        Set side_effect functions.
        '''
        mock_castellan_key_manager.API.side_effect = (
            mock_castellan_api_side_effect)
        mock_castellan_options.set_defaults.side_effect = (
            mock_options_set_defaults_side_effect)
        mock_oslo_config.ConfigOpts.side_effect = (
            mock_config_opts_side_effect)
        '''
        Return invalid Barbican configuration parameters.
        '''
        kms_conf = dict(TEST_KMS_KEYMASTER_CONF)
        kms_conf['key_id'] = TEST_KMS_OPAQUE_KEY_ID
        mock_readconf.return_value = kms_conf

        '''
        Verify that an exception is raised by the mocked function.
        '''
        try:
            self.app = kms_keymaster.KmsKeyMaster(
                self.swift, TEST_PROXYSERVER_CONF_EXTERNAL_KEYMASTER_CONF)
            raise Exception('Success even though key format invalid')
        except ValueError:
            pass
        except Exception:
            print("Unexpected error: %s" % sys.exc_info()[0])
            raise

    @mock.patch('swift.common.middleware.crypto.kms_keymaster.'
                'keystone_password.KeystonePassword')
    @mock.patch('swift.common.middleware.crypto.kms_keymaster.cfg')
    @mock.patch('swift.common.middleware.crypto.kms_keymaster.options')
    @mock.patch('swift.common.middleware.crypto.keymaster.readconf')
    @mock.patch('swift.common.middleware.crypto.kms_keymaster.key_manager')
    def test_mocked_castellan_keymanager_config_file_and_params(
            self, mock_castellan_key_manager, mock_readconf,
            mock_castellan_options, mock_oslo_config,
            mock_keystone_password):
        # Both external config file and config parameters specified.
        mock_keystone_password.side_effect = (
            mock_keystone_password_side_effect)
        '''
        Set side_effect functions.
        '''
        mock_castellan_key_manager.API.side_effect = (
            mock_castellan_api_side_effect)
        mock_castellan_options.set_defaults.side_effect = (
            mock_options_set_defaults_side_effect)
        mock_oslo_config.ConfigOpts.side_effect = (
            mock_config_opts_side_effect)
        '''
        Return invalid Barbican configuration parameters.
        '''
        kms_conf = dict(TEST_KMS_KEYMASTER_CONF)
        kms_conf['keymaster_config_path'] = (
            'PATH_TO_KEYMASTER_CONFIG_FILE'
        )
        mock_readconf.return_value = kms_conf

        '''
        Verify that an exception is raised by the mocked function.
        '''
        try:
            self.app = kms_keymaster.KmsKeyMaster(self.swift, kms_conf)
            raise Exception('Success even though config invalid')
        except Exception as e:
            expected_message = ('keymaster_config_path is set, but there are '
                                'other config options specified:')
            self.assertTrue(e.args[0].startswith(expected_message),
                            "Error message does not start with '%s'" %
                            expected_message)

    @mock.patch('swift.common.middleware.crypto.kms_keymaster.'
                'keystone_password.KeystonePassword')
    @mock.patch('swift.common.middleware.crypto.kms_keymaster.cfg')
    @mock.patch('swift.common.middleware.crypto.kms_keymaster.options')
    @mock.patch('swift.common.middleware.crypto.keymaster.readconf')
    @mock.patch('swift.common.middleware.crypto.kms_keymaster.key_manager')
    def test_mocked_castellan_keymanager_invalid_username(
            self, mock_castellan_key_manager, mock_readconf,
            mock_castellan_options, mock_oslo_config,
            mock_keystone_password):
        # Invalid username.
        mock_keystone_password.side_effect = (
            mock_keystone_password_side_effect)
        '''
        Set side_effect functions.
        '''
        mock_castellan_key_manager.API.side_effect = (
            mock_castellan_api_side_effect)
        mock_castellan_options.set_defaults.side_effect = (
            mock_options_set_defaults_side_effect)
        mock_oslo_config.ConfigOpts.side_effect = (
            mock_config_opts_side_effect)
        '''
        Return invalid Barbican configuration parameters.
        '''
        kms_conf = dict(TEST_KMS_KEYMASTER_CONF)
        kms_conf['username'] = 'invaliduser'
        mock_readconf.return_value = kms_conf

        '''
        Verify that an exception is raised by the mocked function.
        '''
        try:
            self.app = kms_keymaster.KmsKeyMaster(
                self.swift, TEST_PROXYSERVER_CONF_EXTERNAL_KEYMASTER_CONF)
            raise Exception('Success even though username invalid')
        except Unauthorized as e:
            self.assertEqual(e.http_status, 401)
        except Exception:
            print("Unexpected error: %s" % sys.exc_info()[0])
            raise

    @mock.patch('swift.common.middleware.crypto.kms_keymaster.'
                'keystone_password.KeystonePassword')
    @mock.patch('swift.common.middleware.crypto.kms_keymaster.cfg')
    @mock.patch('swift.common.middleware.crypto.kms_keymaster.options')
    @mock.patch('swift.common.middleware.crypto.keymaster.readconf')
    @mock.patch('swift.common.middleware.crypto.kms_keymaster.key_manager')
    def test_mocked_castellan_keymanager_invalid_password(
            self, mock_castellan_key_manager, mock_readconf,
            mock_castellan_options, mock_oslo_config,
            mock_keystone_password):
        # Invalid password.
        mock_keystone_password.side_effect = (
            mock_keystone_password_side_effect)
        '''
        Set side_effect functions.
        '''
        mock_castellan_key_manager.API.side_effect = (
            mock_castellan_api_side_effect)
        mock_castellan_options.set_defaults.side_effect = (
            mock_options_set_defaults_side_effect)
        mock_oslo_config.ConfigOpts.side_effect = (
            mock_config_opts_side_effect)
        '''
        Return invalid Barbican configuration parameters.
        '''
        kms_conf = dict(TEST_KMS_KEYMASTER_CONF)
        kms_conf['password'] = 'invalidpassword'
        mock_readconf.return_value = kms_conf

        '''
        Verify that an exception is raised by the mocked function.
        '''
        try:
            self.app = kms_keymaster.KmsKeyMaster(
                self.swift, TEST_PROXYSERVER_CONF_EXTERNAL_KEYMASTER_CONF)
            raise Exception('Success even though password invalid')
        except Unauthorized as e:
            self.assertEqual(e.http_status, 401)
        except Exception:
            print("Unexpected error: %s" % sys.exc_info()[0])
            raise

    @mock.patch('swift.common.middleware.crypto.kms_keymaster.'
                'keystone_password.KeystonePassword')
    @mock.patch('swift.common.middleware.crypto.kms_keymaster.cfg')
    @mock.patch('swift.common.middleware.crypto.kms_keymaster.options')
    @mock.patch('swift.common.middleware.crypto.keymaster.readconf')
    @mock.patch('swift.common.middleware.crypto.kms_keymaster.key_manager')
    def test_mocked_castellan_keymanager_connect_failure_auth_url(
            self, mock_castellan_key_manager, mock_readconf,
            mock_castellan_options, mock_oslo_config, mock_keystone_password):
        # Connect failure kms auth_url.
        mock_keystone_password.side_effect = (
            mock_keystone_password_side_effect)
        '''
        Set side_effect functions.
        '''
        mock_castellan_key_manager.API.side_effect = (
            mock_castellan_api_side_effect)
        mock_castellan_options.set_defaults.side_effect = (
            mock_options_set_defaults_side_effect)
        mock_oslo_config.ConfigOpts.side_effect = (
            mock_config_opts_side_effect)
        '''
        Return invalid Barbican configuration parameters.
        '''
        kms_conf = dict(TEST_KMS_KEYMASTER_CONF)
        kms_conf['auth_endpoint'] = TEST_KMS_CONNECT_FAILURE_URL
        mock_readconf.return_value = kms_conf

        '''
        Verify that an exception is raised by the mocked function.
        '''
        try:
            self.app = kms_keymaster.KmsKeyMaster(
                self.swift, TEST_PROXYSERVER_CONF_EXTERNAL_KEYMASTER_CONF)
            raise Exception('Success even though auth_url invalid')
        except ConnectFailure:
            pass
        except Exception:
            print("Unexpected error: %s" % sys.exc_info()[0])
            raise

    @mock.patch('swift.common.middleware.crypto.kms_keymaster.'
                'keystone_password.KeystonePassword')
    @mock.patch('swift.common.middleware.crypto.kms_keymaster.cfg')
    @mock.patch('swift.common.middleware.crypto.kms_keymaster.options')
    @mock.patch('swift.common.middleware.crypto.keymaster.readconf')
    @mock.patch('swift.common.middleware.crypto.kms_keymaster.key_manager')
    def test_mocked_castellan_keymanager_bad_auth_url(
            self, mock_castellan_key_manager, mock_readconf,
            mock_castellan_options, mock_oslo_config,
            mock_keystone_password):
        # Bad kms auth_url.
        mock_keystone_password.side_effect = (
            mock_keystone_password_side_effect)
        '''
        Set side_effect functions.
        '''
        mock_castellan_key_manager.API.side_effect = (
            mock_castellan_api_side_effect)
        mock_castellan_options.set_defaults.side_effect = (
            mock_options_set_defaults_side_effect)
        mock_oslo_config.ConfigOpts.side_effect = (
            mock_config_opts_side_effect)
        '''
        Return invalid Barbican configuration parameters.
        '''
        kms_conf = dict(TEST_KMS_KEYMASTER_CONF)
        kms_conf['auth_endpoint'] = TEST_KMS_NON_BARBICAN_URL
        mock_readconf.return_value = kms_conf

        '''
        Verify that an exception is raised by the mocked function.
        '''
        try:
            self.app = kms_keymaster.KmsKeyMaster(
                self.swift, TEST_PROXYSERVER_CONF_EXTERNAL_KEYMASTER_CONF)
            raise Exception('Success even though auth_url invalid')
        except DiscoveryFailure:
            pass
        except Exception:
            print("Unexpected error: %s" % sys.exc_info()[0])
            raise

    @mock.patch('swift.common.middleware.crypto.kms_keymaster.'
                'keystone_password.KeystonePassword')
    @mock.patch('swift.common.middleware.crypto.kms_keymaster.cfg')
    @mock.patch('swift.common.middleware.crypto.kms_keymaster.options')
    @mock.patch('swift.common.middleware.crypto.keymaster.readconf')
    @mock.patch('swift.common.middleware.crypto.kms_keymaster.key_manager')
    def test_mocked_castellan_keymanager_bad_user_domain_name(
            self, mock_castellan_key_manager, mock_readconf,
            mock_castellan_options, mock_oslo_config, mock_keystone_password):
        # Bad user domain name with mocks.
        mock_keystone_password.side_effect = (
            mock_keystone_password_side_effect)
        '''
        Set side_effect functions.
        '''
        mock_castellan_key_manager.API.side_effect = (
            mock_castellan_api_side_effect)
        mock_castellan_options.set_defaults.side_effect = (
            mock_options_set_defaults_side_effect)
        mock_oslo_config.ConfigOpts.side_effect = (
            mock_config_opts_side_effect)
        '''
        Return invalid Barbican configuration parameters.
        '''
        kms_conf = dict(TEST_KMS_KEYMASTER_CONF)
        kms_conf['user_domain_name'] = (
            TEST_KMS_INVALID_USER_DOMAIN_NAME)
        mock_readconf.return_value = kms_conf

        '''
        Verify that an exception is raised by the mocked function.
        '''
        try:
            self.app = kms_keymaster.KmsKeyMaster(
                self.swift, TEST_PROXYSERVER_CONF_EXTERNAL_KEYMASTER_CONF)
            raise Exception('Success even though api_version invalid')
        except Unauthorized as e:
            self.assertEqual(e.http_status, 401)
        except Exception:
            print("Unexpected error: %s" % sys.exc_info()[0])
            raise

    @mock.patch('swift.common.middleware.crypto.kms_keymaster.'
                'keystone_password.KeystonePassword')
    @mock.patch('swift.common.middleware.crypto.kms_keymaster.cfg')
    @mock.patch('swift.common.middleware.crypto.kms_keymaster.options')
    @mock.patch('swift.common.middleware.crypto.keymaster.readconf')
    @mock.patch('swift.common.middleware.crypto.kms_keymaster.key_manager')
    def test_mocked_castellan_keymanager_invalid_key_algorithm(
            self, mock_castellan_key_manager, mock_readconf,
            mock_castellan_options, mock_oslo_config,
            mock_keystone_password):
        # Nonexistent key.
        mock_keystone_password.side_effect = (
            mock_keystone_password_side_effect)
        '''
        Set side_effect functions.
        '''
        mock_castellan_key_manager.API.side_effect = (
            mock_castellan_api_side_effect)
        mock_castellan_options.set_defaults.side_effect = (
            mock_options_set_defaults_side_effect)
        mock_oslo_config.ConfigOpts.side_effect = (
            mock_config_opts_side_effect)
        '''
        Return invalid Barbican configuration parameters.
        '''
        kms_conf = dict(TEST_KMS_KEYMASTER_CONF)
        kms_conf['key_id'] = TEST_KMS_DES_KEY_ID
        mock_readconf.return_value = kms_conf

        '''
        Verify that an exception is raised by the mocked function.
        '''
        try:
            self.app = kms_keymaster.KmsKeyMaster(
                self.swift, TEST_PROXYSERVER_CONF_EXTERNAL_KEYMASTER_CONF)
            raise Exception('Success even though key format invalid')
        except ValueError:
            pass
        except Exception:
            print("Unexpected error: %s" % sys.exc_info()[0])
            raise

    @mock.patch('swift.common.middleware.crypto.kms_keymaster.'
                'keystone_password.KeystonePassword')
    @mock.patch('swift.common.middleware.crypto.kms_keymaster.cfg')
    @mock.patch('swift.common.middleware.crypto.kms_keymaster.options')
    @mock.patch('swift.common.middleware.crypto.keymaster.readconf')
    @mock.patch('swift.common.middleware.crypto.kms_keymaster.key_manager')
    def test_mocked_castellan_keymanager_invalid_key_length(
            self, mock_castellan_key_manager, mock_readconf,
            mock_castellan_options, mock_oslo_config,
            mock_keystone_password):
        # Nonexistent key.
        mock_keystone_password.side_effect = (
            mock_keystone_password_side_effect)
        '''
        Set side_effect functions.
        '''
        mock_castellan_key_manager.API.side_effect = (
            mock_castellan_api_side_effect)
        mock_castellan_options.set_defaults.side_effect = (
            mock_options_set_defaults_side_effect)
        mock_oslo_config.ConfigOpts.side_effect = (
            mock_config_opts_side_effect)
        '''
        Return invalid Barbican configuration parameters.
        '''
        kms_conf = dict(TEST_KMS_KEYMASTER_CONF)
        kms_conf['key_id'] = TEST_KMS_SHORT_KEY_ID
        mock_readconf.return_value = kms_conf

        '''
        Verify that an exception is raised by the mocked function.
        '''
        try:
            self.app = kms_keymaster.KmsKeyMaster(
                self.swift, TEST_PROXYSERVER_CONF_EXTERNAL_KEYMASTER_CONF)
            raise Exception('Success even though key format invalid')
        except ValueError:
            pass
        except Exception:
            print("Unexpected error: %s" % sys.exc_info()[0])
            raise

    @mock.patch('swift.common.middleware.crypto.kms_keymaster.'
                'keystone_password.KeystonePassword')
    @mock.patch('swift.common.middleware.crypto.kms_keymaster.cfg')
    @mock.patch('swift.common.middleware.crypto.kms_keymaster.options')
    @mock.patch('swift.common.middleware.crypto.keymaster.readconf')
    @mock.patch('swift.common.middleware.crypto.kms_keymaster.key_manager')
    def test_mocked_castellan_keymanager_none_key(
            self, mock_castellan_key_manager, mock_readconf,
            mock_castellan_options, mock_oslo_config,
            mock_keystone_password):
        # Nonexistent key.
        mock_keystone_password.side_effect = (
            mock_keystone_password_side_effect)
        '''
        Set side_effect functions.
        '''
        mock_castellan_key_manager.API.side_effect = (
            mock_castellan_api_side_effect)
        mock_castellan_options.set_defaults.side_effect = (
            mock_options_set_defaults_side_effect)
        mock_oslo_config.ConfigOpts.side_effect = (
            mock_config_opts_side_effect)
        '''
        Return invalid Barbican configuration parameters.
        '''
        kms_conf = dict(TEST_KMS_KEYMASTER_CONF)
        kms_conf['key_id'] = TEST_KMS_NONE_KEY_ID
        mock_readconf.return_value = kms_conf

        '''
        Verify that an exception is raised by the mocked function.
        '''
        try:
            self.app = kms_keymaster.KmsKeyMaster(
                self.swift, TEST_PROXYSERVER_CONF_EXTERNAL_KEYMASTER_CONF)
            raise Exception('Success even though None key returned')
        except ValueError:
            pass
        except Exception:
            print("Unexpected error: %s" % sys.exc_info()[0])
            raise

    @mock.patch('swift.common.middleware.crypto.kms_keymaster.'
                'keystone_password.KeystonePassword', MockPassword)
    @mock.patch('swift.common.middleware.crypto.kms_keymaster.cfg')
    @mock.patch('swift.common.middleware.crypto.kms_keymaster.options')
    @mock.patch('swift.common.middleware.crypto.keymaster.readconf')
    @mock.patch('swift.common.middleware.crypto.kms_keymaster.key_manager')
    def test_get_root_secret_multiple_keys(
            self, mock_castellan_key_manager, mock_readconf,
            mock_castellan_options, mock_oslo_config,):
        config = dict(TEST_KMS_KEYMASTER_CONF)
        config.update({
            'key_id_foo': 'foo-valid_kms_key_id-123456',
            'key_id_bar': 'bar-valid_kms_key_id-123456',
            'key_id_baz': 'zz-valid_unicode_kms_key_id-123456',
            'key_id_non_ascii': u'\N{SNOWMAN}_unicode_key_id',
            'active_root_secret_id': 'foo'})

        # Set side_effect functions.
        mock_castellan_key_manager.API.side_effect = (
            mock_castellan_api_side_effect)
        mock_castellan_options.set_defaults.side_effect = (
            mock_options_set_defaults_side_effect)
        mock_oslo_config.ConfigOpts.side_effect = (
            mock_config_opts_side_effect)

        # Return valid Barbican configuration parameters.
        mock_readconf.return_value = config

        self.app = kms_keymaster.KmsKeyMaster(self.swift,
                                              config)

        expected_secrets = {
            None: b'vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv',
            'foo': b'ffffffffffffffffffffffffffffffff',
            'bar': b'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',
            'baz': b'zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz',
            'non_ascii': b'\xe2\x98\x83' * 32}
        self.assertDictEqual(self.app._root_secrets, expected_secrets)
        self.assertEqual(self.app.active_secret_id, 'foo')

    @mock.patch('swift.common.middleware.crypto.kms_keymaster.'
                'keystone_password.KeystonePassword', MockPassword)
    @mock.patch('swift.common.middleware.crypto.kms_keymaster.cfg')
    @mock.patch('swift.common.middleware.crypto.kms_keymaster.options')
    @mock.patch('swift.common.middleware.crypto.keymaster.readconf')
    @mock.patch('swift.common.middleware.crypto.kms_keymaster.key_manager')
    def test_get_root_secret_legacy_key_id(
            self, mock_castellan_key_manager, mock_readconf,
            mock_castellan_options, mock_oslo_config):

        # Set side_effect functions.
        mock_castellan_key_manager.API.side_effect = (
            mock_castellan_api_side_effect)
        mock_castellan_options.set_defaults.side_effect = (
            mock_options_set_defaults_side_effect)
        mock_oslo_config.ConfigOpts.side_effect = (
            mock_config_opts_side_effect)

        # Return valid Barbican configuration parameters.
        mock_readconf.return_value = TEST_KMS_KEYMASTER_CONF

        self.app = kms_keymaster.KmsKeyMaster(self.swift,
                                              TEST_KMS_KEYMASTER_CONF)

        expected_secrets = {None: b'vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv'}
        self.assertDictEqual(self.app._root_secrets, expected_secrets)
        self.assertIsNone(self.app.active_secret_id)


if __name__ == '__main__':
    unittest.main()
