Merge pull request #143 from OpenKMIP/feat/add-kmip-engine-destroy

Adding KmipEngine support for Destroy
This commit is contained in:
Peter Hamilton 2016-03-15 16:14:13 -04:00
commit 9059172a08
3 changed files with 343 additions and 5 deletions

View File

@ -57,6 +57,24 @@ class CryptographicFailure(KmipError):
)
class IndexOutOfBounds(KmipError):
"""
An error generated when exceeding the attribute instance limit.
"""
def __init__(self, message):
"""
Create an IndexOutOfBounds exception.
Args:
message (string): A string containing information about the error.
"""
super(IndexOutOfBounds, self).__init__(
reason=enums.ResultReason.INDEX_OUT_OF_BOUNDS,
message=message
)
class InvalidField(KmipError):
"""
An error generated when an invalid field value is processed.
@ -93,6 +111,24 @@ class InvalidMessage(KmipError):
)
class ItemNotFound(KmipError):
"""
An error generated when a request item cannot be located.
"""
def __init__(self, message):
"""
Create an ItemNotFound exception.
Args:
message (string): A string containing information about the error.
"""
super(ItemNotFound, self).__init__(
reason=enums.ResultReason.ITEM_NOT_FOUND,
message=message
)
class OperationNotSupported(KmipError):
"""
An error generated when an unsupported operation is invoked.

View File

@ -14,22 +14,31 @@
# under the License.
import logging
import sqlalchemy
from sqlalchemy.orm import exc
import threading
import time
import kmip
from kmip.core import attributes
from kmip.core import enums
from kmip.core import exceptions
from kmip.core.messages import contents
from kmip.core.messages import messages
from kmip.core.messages.payloads import destroy
from kmip.core.messages.payloads import discover_versions
from kmip.core.messages.payloads import query
from kmip.core import misc
from kmip.pie import sqltypes
from kmip.pie import objects
from kmip.services.server.crypto import engine
@ -47,6 +56,8 @@ class KmipEngine(object):
* User authentication
* Batch processing options: UNDO
* Asynchronous operations
* Operation policies
* Object archival
"""
def __init__(self):
@ -56,6 +67,16 @@ class KmipEngine(object):
self._logger = logging.getLogger(__name__)
self._cryptography_engine = engine.CryptographyEngine()
self._data_store = sqlalchemy.create_engine(
'sqlite:///:memory:',
echo=False
)
sqltypes.Base.metadata.create_all(self._data_store)
self._data_store_session_factory = sqlalchemy.orm.sessionmaker(
bind=self._data_store
)
self._lock = threading.RLock()
self._id_placeholder = None
@ -68,6 +89,17 @@ class KmipEngine(object):
self._protocol_version = self._protocol_versions[0]
self._object_map = {
enums.ObjectType.CERTIFICATE: objects.X509Certificate,
enums.ObjectType.SYMMETRIC_KEY: objects.SymmetricKey,
enums.ObjectType.PUBLIC_KEY: objects.PublicKey,
enums.ObjectType.PRIVATE_KEY: objects.PrivateKey,
enums.ObjectType.SPLIT_KEY: None,
enums.ObjectType.TEMPLATE: None,
enums.ObjectType.SECRET_DATA: objects.SecretData,
enums.ObjectType.OPAQUE_DATA: objects.OpaqueObject
}
def _kmip_version_supported(supported):
def decorator(function):
def wrapper(self, *args, **kwargs):
@ -266,6 +298,9 @@ class KmipEngine(object):
def _process_batch(self, request_batch, batch_handling, batch_order):
response_batch = list()
self._data_session = self._data_store_session_factory()
for batch_item in request_batch:
error_occurred = False
@ -342,6 +377,8 @@ class KmipEngine(object):
return response_batch
def _process_operation(self, operation, payload):
if operation == enums.Operation.DESTROY:
return self._process_destroy(payload)
if operation == enums.Operation.QUERY:
return self._process_query(payload)
elif operation == enums.Operation.DISCOVER_VERSIONS:
@ -353,6 +390,64 @@ class KmipEngine(object):
)
)
@_kmip_version_supported('1.0')
def _process_destroy(self, payload):
self._logger.info("Processing operation: Destroy")
if payload.unique_identifier:
unique_identifier = payload.unique_identifier.value
else:
unique_identifier = self._id_placeholder
try:
object_type = self._data_session.query(
objects.ManagedObject._object_type
).filter(
objects.ManagedObject.unique_identifier == unique_identifier
).one()[0]
except exc.NoResultFound as e:
self._logger.warning(
"Could not identify object type for object: {0}".format(
unique_identifier
)
)
self._logger.exception(e)
raise exceptions.ItemNotFound(
"Could not locate object: {0}".format(unique_identifier)
)
except exc.MultipleResultsFound as e:
self._logger.warning(
"Multiple objects found for ID: {0}".format(
unique_identifier
)
)
raise e
table = self._object_map.get(object_type)
if table is None:
name = object_type.name
raise exceptions.InvalidField(
"The {0} object type is not supported.".format(
''.join(
[x.capitalize() for x in name[9:].split('_')]
)
)
)
# TODO (peterhamilton) Process attributes to see if destroy possible
# 1. Check object state. If invalid, error out.
# 2. Check object deactivation date. If invalid, error out.
self._data_session.query(table).filter(
table.unique_identifier == unique_identifier
).delete()
response_payload = destroy.DestroyResponsePayload(
unique_identifier=attributes.UniqueIdentifier(unique_identifier)
)
return response_payload
@_kmip_version_supported('1.0')
def _process_query(self, payload):
self._logger.info("Processing operation: Query")
@ -368,6 +463,7 @@ class KmipEngine(object):
if enums.QueryFunction.QUERY_OPERATIONS in queries:
operations = list([
contents.Operation(enums.Operation.DESTROY),
contents.Operation(enums.Operation.QUERY)
])

View File

@ -14,11 +14,16 @@
# under the License.
import mock
import sqlalchemy
from sqlalchemy.orm import exc
import testtools
import time
import kmip
from kmip.core import attributes
from kmip.core import enums
from kmip.core import exceptions
from kmip.core import misc
@ -27,9 +32,13 @@ from kmip.core import objects
from kmip.core.messages import contents
from kmip.core.messages import messages
from kmip.core.messages.payloads import destroy
from kmip.core.messages.payloads import discover_versions
from kmip.core.messages.payloads import query
from kmip.pie import objects as pie_objects
from kmip.pie import sqltypes
from kmip.services.server import engine
@ -50,6 +59,14 @@ class TestKmipEngine(testtools.TestCase):
def setUp(self):
super(TestKmipEngine, self).setUp()
self.engine = sqlalchemy.create_engine(
'sqlite:///:memory:',
)
sqltypes.Base.metadata.create_all(self.engine)
self.session_factory = sqlalchemy.orm.sessionmaker(
bind=self.engine
)
def tearDown(self):
super(TestKmipEngine, self).tearDown()
@ -621,12 +638,15 @@ class TestKmipEngine(testtools.TestCase):
e = engine.KmipEngine()
e._logger = mock.MagicMock()
e._process_destroy = mock.MagicMock()
e._process_query = mock.MagicMock()
e._process_discover_versions = mock.MagicMock()
e._process_operation(enums.Operation.DESTROY, None)
e._process_operation(enums.Operation.QUERY, None)
e._process_operation(enums.Operation.DISCOVER_VERSIONS, None)
e._process_destroy.assert_called_with(None)
e._process_query.assert_called_with(None)
e._process_discover_versions.assert_called_with(None)
@ -649,6 +669,178 @@ class TestKmipEngine(testtools.TestCase):
*args
)
def test_destroy(self):
"""
Test that a Destroy request can be processed correctly.
"""
e = engine.KmipEngine()
e._data_store = self.engine
e._data_store_session_factory = self.session_factory
e._data_session = e._data_store_session_factory()
e._logger = mock.MagicMock()
obj_a = pie_objects.OpaqueObject(b'', enums.OpaqueDataType.NONE)
obj_b = pie_objects.OpaqueObject(b'', enums.OpaqueDataType.NONE)
e._data_session.add(obj_a)
e._data_session.add(obj_b)
e._data_session.commit()
e._data_session = e._data_store_session_factory()
id_a = str(obj_a.unique_identifier)
id_b = str(obj_b.unique_identifier)
# Test by specifying the ID of the object to destroy.
payload = destroy.DestroyRequestPayload(
unique_identifier=attributes.UniqueIdentifier(id_a)
)
response_payload = e._process_destroy(payload)
e._data_session.commit()
e._data_session = e._data_store_session_factory()
e._logger.info.assert_called_once_with(
"Processing operation: Destroy"
)
self.assertEqual(str(id_a), response_payload.unique_identifier.value)
self.assertRaises(
exc.NoResultFound,
e._data_session.query(pie_objects.OpaqueObject).filter(
pie_objects.ManagedObject.unique_identifier == id_a
).one
)
e._data_session.commit()
e._data_store_session_factory()
e._logger.reset_mock()
e._id_placeholder = str(id_b)
# Test by using the ID placeholder to specify the object to destroy.
payload = destroy.DestroyRequestPayload()
response_payload = e._process_destroy(payload)
e._data_session.commit()
e._data_session = e._data_store_session_factory()
e._logger.info.assert_called_once_with(
"Processing operation: Destroy"
)
self.assertEqual(str(id_b), response_payload.unique_identifier.value)
self.assertRaises(
exc.NoResultFound,
e._data_session.query(pie_objects.OpaqueObject).filter(
pie_objects.ManagedObject.unique_identifier == id_b
).one
)
e._data_session.commit()
def test_destroy_missing_object(self):
"""
Test that an ItemNotFound error is generated when attempting to
destroy an object that does not exist.
"""
e = engine.KmipEngine()
e._data_store = self.engine
e._data_store_session_factory = self.session_factory
e._data_session = e._data_store_session_factory()
e._logger = mock.MagicMock()
payload = destroy.DestroyRequestPayload(
unique_identifier=attributes.UniqueIdentifier('1')
)
args = (payload, )
regex = "Could not locate object: 1"
self.assertRaisesRegexp(
exceptions.ItemNotFound,
regex,
e._process_destroy,
*args
)
e._data_session.commit()
e._logger.info.assert_called_once_with(
"Processing operation: Destroy"
)
e._logger.warning.assert_called_once_with(
"Could not identify object type for object: 1"
)
self.assertTrue(e._logger.exception.called)
def test_destroy_multiple_objects(self):
"""
Test that a sqlalchemy.orm.exc.MultipleResultsFound error is generated
when multiple objects map to the same object ID.
"""
e = engine.KmipEngine()
e._data_store = self.engine
e._data_store_session_factory = self.session_factory
e._data_session = e._data_store_session_factory()
test_exception = exc.MultipleResultsFound()
e._data_session.query = mock.MagicMock(side_effect=test_exception)
e._logger = mock.MagicMock()
payload = destroy.DestroyRequestPayload(
unique_identifier=attributes.UniqueIdentifier('1')
)
args = (payload, )
self.assertRaises(
exc.MultipleResultsFound,
e._process_destroy,
*args
)
e._data_session.commit()
e._logger.info.assert_called_once_with(
"Processing operation: Destroy"
)
e._logger.warning.assert_called_once_with(
"Multiple objects found for ID: 1"
)
def test_destroy_unsupported_object_type(self):
"""
Test that an InvalidField error is generated when attempting to
destroy an unsupported object type.
"""
e = engine.KmipEngine()
e._object_map = {enums.ObjectType.OPAQUE_DATA: None}
e._data_store = self.engine
e._data_store_session_factory = self.session_factory
e._data_session = e._data_store_session_factory()
e._logger = mock.MagicMock()
obj_a = pie_objects.OpaqueObject(b'', enums.OpaqueDataType.NONE)
e._data_session.add(obj_a)
e._data_session.commit()
e._data_session = e._data_store_session_factory()
id_a = str(obj_a.unique_identifier)
payload = destroy.DestroyRequestPayload(
unique_identifier=attributes.UniqueIdentifier(id_a)
)
args = (payload, )
name = enums.ObjectType.OPAQUE_DATA.name
regex = "The {0} object type is not supported.".format(
''.join(
[x.capitalize() for x in name[9:].split('_')]
)
)
self.assertRaisesRegexp(
exceptions.InvalidField,
regex,
e._process_destroy,
*args
)
e._data_session.commit()
e._logger.info.assert_called_once_with(
"Processing operation: Destroy"
)
def test_query(self):
"""
Test that a Query request can be processed correctly, for different
@ -678,8 +870,15 @@ class TestKmipEngine(testtools.TestCase):
e._logger.info.assert_called_once_with("Processing operation: Query")
self.assertIsInstance(result, query.QueryResponsePayload)
self.assertIsNotNone(result.operations)
self.assertEqual(1, len(result.operations))
self.assertEqual(enums.Operation.QUERY, result.operations[0].value)
self.assertEqual(2, len(result.operations))
self.assertEqual(
enums.Operation.DESTROY,
result.operations[0].value
)
self.assertEqual(
enums.Operation.QUERY,
result.operations[1].value
)
self.assertEqual(list(), result.object_types)
self.assertIsNotNone(result.vendor_identification)
self.assertEqual(
@ -698,11 +897,18 @@ class TestKmipEngine(testtools.TestCase):
e._logger.info.assert_called_once_with("Processing operation: Query")
self.assertIsNotNone(result.operations)
self.assertEqual(2, len(result.operations))
self.assertEqual(enums.Operation.QUERY, result.operations[0].value)
self.assertEqual(3, len(result.operations))
self.assertEqual(
enums.Operation.DESTROY,
result.operations[0].value
)
self.assertEqual(
enums.Operation.QUERY,
result.operations[1].value
)
self.assertEqual(
enums.Operation.DISCOVER_VERSIONS,
result.operations[1].value
result.operations[2].value
)
def test_discover_versions(self):