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:]) opts, args = parser.parse_args(sys.argv[1:])
config = opts.config config = opts.config
offset_items = opts.offset_items
maximum_items = opts.maximum_items
name = opts.name name = opts.name
initial_dates = opts.initial_dates initial_dates = opts.initial_dates
state = opts.state state = opts.state
@ -43,6 +45,13 @@ if __name__ == '__main__':
attribute_factory = AttributeFactory() 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 # Build attributes if any are specified
attributes = [] attributes = []
if name: if name:
@ -159,7 +168,11 @@ if __name__ == '__main__':
config_file=opts.config_file config_file=opts.config_file
) as client: ) as client:
try: 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)) logger.info("Located uuids: {0}".format(uuids))
except Exception as e: except Exception as e:
logger.error(e) logger.error(e)

View File

@ -35,6 +35,8 @@ if __name__ == '__main__':
username = opts.username username = opts.username
password = opts.password password = opts.password
config = opts.config config = opts.config
offset_items = opts.offset_items
maximum_items = opts.maximum_items
name = opts.name name = opts.name
initial_dates = opts.initial_dates initial_dates = opts.initial_dates
state = opts.state state = opts.state
@ -62,6 +64,13 @@ if __name__ == '__main__':
credential_value 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 # Build the client and connect to the server
client = kmip_client.KMIPProxy(config=config, config_file=opts.config_file) client = kmip_client.KMIPProxy(config=config, config_file=opts.config_file)
client.open() 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() client.close()
# Display operation results # 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 " help="List of attribute names to retrieve, defaults to all "
"attributes") "attributes")
elif operation is Operation.LOCATE: 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( parser.add_option(
"-n", "-n",
"--name", "--name",

View File

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

View File

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

View File

@ -1780,6 +1780,22 @@ class KmipEngine(object):
reverse=True 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 = [ unique_identifiers = [
str(x.unique_identifier) for x in managed_objects 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)) 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 # Clean up keys
result = self.client.destroy(uid_a) result = self.client.destroy(uid_a)
self.assertEqual(ResultStatus.SUCCESS, result.result_status.value) self.assertEqual(ResultStatus.SUCCESS, result.result_status.value)

View File

@ -1132,6 +1132,22 @@ class TestProxyKmipClientIntegration(testtools.TestCase):
) )
self.assertEqual(0, len(result)) 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 # Clean up the keys
self.client.destroy(a_id) self.client.destroy(a_id)
self.client.destroy(b_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_a, response_payload.unique_identifiers)
self.assertIn(id_b, 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): def test_locate_with_name(self):
""" """
Test locate operation when 'Name' attribute is given. Test locate operation when 'Name' attribute is given.