Add offset and maximum item filtering for the Locate operation

This change updates Locate operation support in the PyKMIP server,
allowing users to filter objects using the offset and maximum item
constraints. The offset constraint tells the server how many
matching items should be skipped before results are returned. The
maximum items constraint tells the server how many matching items
should be returned. Unit tests and integration tests have been
added to test and verify the correctness of this feature.

Additionally, the Locate demo scripts have also been updated to
support offset and maximum item filtering. Simply use the
"--offset-items" and "--maximum-items" flags to specify offset and
maximum item values for the Locate script to filter on.

Fixes #562
This commit is contained in:
Peter Hamilton 2019-08-09 16:37:39 -04:00 committed by Peter Hamilton
parent 4938f82772
commit 4a6a2eccc1
9 changed files with 210 additions and 8 deletions

View File

@ -32,6 +32,8 @@ if __name__ == '__main__':
opts, args = parser.parse_args(sys.argv[1:])
config = opts.config
offset_items = opts.offset_items
maximum_items = opts.maximum_items
name = opts.name
initial_dates = opts.initial_dates
state = opts.state
@ -43,6 +45,13 @@ if __name__ == '__main__':
attribute_factory = AttributeFactory()
if offset_items and (offset_items < 0):
logger.error("Invalid offset items value provided.")
sys.exit(-1)
if maximum_items and (maximum_items < 0):
logger.error("Invalid maximum items value provided.")
sys.exit(-1)
# Build attributes if any are specified
attributes = []
if name:
@ -159,7 +168,11 @@ if __name__ == '__main__':
config_file=opts.config_file
) as client:
try:
uuids = client.locate(attributes=attributes)
uuids = client.locate(
attributes=attributes,
offset_items=offset_items,
maximum_items=maximum_items
)
logger.info("Located uuids: {0}".format(uuids))
except Exception as e:
logger.error(e)

View File

@ -35,6 +35,8 @@ if __name__ == '__main__':
username = opts.username
password = opts.password
config = opts.config
offset_items = opts.offset_items
maximum_items = opts.maximum_items
name = opts.name
initial_dates = opts.initial_dates
state = opts.state
@ -62,6 +64,13 @@ if __name__ == '__main__':
credential_value
)
if offset_items and (offset_items < 0):
logger.error("Invalid offset items value provided.")
sys.exit(-1)
if maximum_items and (maximum_items < 0):
logger.error("Invalid maximum items value provided.")
sys.exit(-1)
# Build the client and connect to the server
client = kmip_client.KMIPProxy(config=config, config_file=opts.config_file)
client.open()
@ -180,7 +189,12 @@ if __name__ == '__main__':
)
)
result = client.locate(attributes=attributes, credential=credential)
result = client.locate(
attributes=attributes,
offset_items=offset_items,
maximum_items=maximum_items,
credential=credential
)
client.close()
# Display operation results

View File

@ -230,6 +230,22 @@ def build_cli_parser(operation=None):
help="List of attribute names to retrieve, defaults to all "
"attributes")
elif operation is Operation.LOCATE:
parser.add_option(
"--offset-items",
action="store",
type="int",
default=None,
dest="offset_items",
help="The number of matching secrets to skip."
)
parser.add_option(
"--maximum-items",
action="store",
type="int",
default=None,
dest="maximum_items",
help="The maximum number of matching secrets to return."
)
parser.add_option(
"-n",
"--name",

View File

@ -661,7 +661,7 @@ class ProxyKmipClient(object):
@is_connected
def locate(self, maximum_items=None, storage_status_mask=None,
object_group_member=None, attributes=None):
object_group_member=None, attributes=None, offset_items=None):
"""
Search for managed objects, depending on the attributes specified in
the request.
@ -669,6 +669,8 @@ class ProxyKmipClient(object):
Args:
maximum_items (integer): Maximum number of object identifiers the
server MAY return.
offset_items (integer): Number of object identifiers the server
should skip before returning results.
storage_status_mask (integer): A bit mask that indicates whether
on-line or archived objects are to be searched.
object_group_member (ObjectGroupMember): An enumeration that
@ -688,6 +690,9 @@ class ProxyKmipClient(object):
if maximum_items is not None:
if not isinstance(maximum_items, six.integer_types):
raise TypeError("maximum_items must be an integer")
if offset_items is not None:
if not isinstance(offset_items, six.integer_types):
raise TypeError("offset items must be an integer")
if storage_status_mask is not None:
if not isinstance(storage_status_mask, six.integer_types):
raise TypeError("storage_status_mask must be an integer")
@ -705,8 +710,12 @@ class ProxyKmipClient(object):
# Search for managed objects and handle the results
result = self.proxy.locate(
maximum_items, storage_status_mask,
object_group_member, attributes)
maximum_items=maximum_items,
offset_items=offset_items,
storage_status_mask=storage_status_mask,
object_group_member=object_group_member,
attributes=attributes
)
status = result.result_status.value
if status == enums.ResultStatus.SUCCESS:

View File

@ -694,11 +694,13 @@ class KMIPProxy(object):
return results[0]
def locate(self, maximum_items=None, storage_status_mask=None,
object_group_member=None, attributes=None, credential=None):
object_group_member=None, attributes=None, credential=None,
offset_items=None):
return self._locate(maximum_items=maximum_items,
storage_status_mask=storage_status_mask,
object_group_member=object_group_member,
attributes=attributes, credential=credential)
attributes=attributes, credential=credential,
offset_items=offset_items)
def query(self, batch=False, query_functions=None, credential=None):
"""
@ -1476,12 +1478,14 @@ class KMIPProxy(object):
return result
def _locate(self, maximum_items=None, storage_status_mask=None,
object_group_member=None, attributes=None, credential=None):
object_group_member=None, attributes=None, credential=None,
offset_items=None):
operation = Operation(OperationEnum.LOCATE)
payload = payloads.LocateRequestPayload(
maximum_items=maximum_items,
offset_items=offset_items,
storage_status_mask=storage_status_mask,
object_group_member=object_group_member,
attributes=attributes

View File

@ -1780,6 +1780,22 @@ class KmipEngine(object):
reverse=True
)
# Skip the requested offset items and keep the requested maximum items
if payload.offset_items is not None:
if payload.maximum_items is not None:
managed_objects = managed_objects[
payload.offset_items:(
payload.offset_items + payload.maximum_items
)
]
else:
managed_objects = managed_objects[payload.offset_items:]
else:
if payload.maximum_items is not None:
managed_objects = managed_objects[:payload.maximum_items]
else:
pass
unique_identifiers = [
str(x.unique_identifier) for x in managed_objects
]

View File

@ -1477,6 +1477,22 @@ class TestIntegration(testtools.TestCase):
)
self.assertEqual(0, len(result.uuids))
# Test locating keys using offset and maximum item constraints.
result = self.client.locate(offset_items=1)
self.assertEqual(1, len(result.uuids))
self.assertIn(uid_a, result.uuids)
result = self.client.locate(maximum_items=1)
self.assertEqual(1, len(result.uuids))
self.assertIn(uid_b, result.uuids)
result = self.client.locate(offset_items=1, maximum_items=1)
self.assertEqual(1, len(result.uuids))
self.assertIn(uid_a, result.uuids)
# Clean up keys
result = self.client.destroy(uid_a)
self.assertEqual(ResultStatus.SUCCESS, result.result_status.value)

View File

@ -1132,6 +1132,22 @@ class TestProxyKmipClientIntegration(testtools.TestCase):
)
self.assertEqual(0, len(result))
# Test locating keys using offset and maximum item constraints.
result = self.client.locate(offset_items=1)
self.assertEqual(1, len(result))
self.assertIn(a_id, result)
result = self.client.locate(maximum_items=1)
self.assertEqual(1, len(result))
self.assertIn(b_id, result)
result = self.client.locate(offset_items=1, maximum_items=1)
self.assertEqual(1, len(result))
self.assertIn(a_id, result)
# Clean up the keys
self.client.destroy(a_id)
self.client.destroy(b_id)

View File

@ -4474,6 +4474,104 @@ class TestKmipEngine(testtools.TestCase):
self.assertIn(id_a, response_payload.unique_identifiers)
self.assertIn(id_b, response_payload.unique_identifiers)
def test_locate_with_offset_and_maximum_items(self):
"""
Test locate operation with specified offset and maximum item limits.
"""
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._is_allowed_by_operation_policy = mock.Mock(return_value=True)
e._logger = mock.MagicMock()
key = (
b'\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
)
obj_a = pie_objects.SymmetricKey(
enums.CryptographicAlgorithm.AES,
128,
key,
name='name1'
)
obj_a.initial_date = int(time.time())
time.sleep(2)
obj_b = pie_objects.SymmetricKey(
enums.CryptographicAlgorithm.DES,
128,
key,
name='name2'
)
obj_b.initial_date = int(time.time())
time.sleep(2)
obj_c = pie_objects.SymmetricKey(
enums.CryptographicAlgorithm.AES,
128,
key,
name='name3'
)
obj_c.initial_date = int(time.time())
e._data_session.add(obj_a)
e._data_session.add(obj_b)
e._data_session.add(obj_c)
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)
id_c = str(obj_c.unique_identifier)
# Locate all objects.
payload = payloads.LocateRequestPayload()
e._logger.reset_mock()
response_payload = e._process_locate(payload)
e._data_session.commit()
e._data_session = e._data_store_session_factory()
e._logger.info.assert_any_call("Processing operation: Locate")
self.assertEqual(
[id_c, id_b, id_a],
response_payload.unique_identifiers
)
# Locate by skipping the first object and only returning one object.
payload = payloads.LocateRequestPayload(
offset_items=1,
maximum_items=1
)
e._logger.reset_mock()
response_payload = e._process_locate(payload)
e._data_session.commit()
e._data_session = e._data_store_session_factory()
e._logger.info.assert_any_call("Processing operation: Locate")
self.assertEqual([id_b], response_payload.unique_identifiers)
# Locate by skipping the first two objects.
payload = payloads.LocateRequestPayload(offset_items=2)
e._logger.reset_mock()
response_payload = e._process_locate(payload)
e._data_session.commit()
e._data_session = e._data_store_session_factory()
e._logger.info.assert_any_call("Processing operation: Locate")
self.assertEqual([id_a], response_payload.unique_identifiers)
# Locate by only returning two objects.
payload = payloads.LocateRequestPayload(maximum_items=2)
e._logger.reset_mock()
response_payload = e._process_locate(payload)
e._data_session.commit()
e._data_session = e._data_store_session_factory()
e._logger.info.assert_any_call("Processing operation: Locate")
self.assertEqual([id_c, id_b], response_payload.unique_identifiers)
def test_locate_with_name(self):
"""
Test locate operation when 'Name' attribute is given.