diff --git a/kmip/demos/pie/locate.py b/kmip/demos/pie/locate.py index 10f3b50..f7908c6 100644 --- a/kmip/demos/pie/locate.py +++ b/kmip/demos/pie/locate.py @@ -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) diff --git a/kmip/demos/units/locate.py b/kmip/demos/units/locate.py index 7f8c5ca..f46ee00 100644 --- a/kmip/demos/units/locate.py +++ b/kmip/demos/units/locate.py @@ -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 diff --git a/kmip/demos/utils.py b/kmip/demos/utils.py index 6cd6a8d..f4308d8 100644 --- a/kmip/demos/utils.py +++ b/kmip/demos/utils.py @@ -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", diff --git a/kmip/pie/client.py b/kmip/pie/client.py index 4d58615..2d23636 100644 --- a/kmip/pie/client.py +++ b/kmip/pie/client.py @@ -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: diff --git a/kmip/services/kmip_client.py b/kmip/services/kmip_client.py index f87e65a..38bbc44 100644 --- a/kmip/services/kmip_client.py +++ b/kmip/services/kmip_client.py @@ -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 diff --git a/kmip/services/server/engine.py b/kmip/services/server/engine.py index a64ae4f..73cc9e9 100644 --- a/kmip/services/server/engine.py +++ b/kmip/services/server/engine.py @@ -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 ] diff --git a/kmip/tests/integration/services/test_integration.py b/kmip/tests/integration/services/test_integration.py index 631d2d3..eaceb69 100644 --- a/kmip/tests/integration/services/test_integration.py +++ b/kmip/tests/integration/services/test_integration.py @@ -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) diff --git a/kmip/tests/integration/services/test_proxykmipclient.py b/kmip/tests/integration/services/test_proxykmipclient.py index 26e28b0..d38185a 100644 --- a/kmip/tests/integration/services/test_proxykmipclient.py +++ b/kmip/tests/integration/services/test_proxykmipclient.py @@ -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) diff --git a/kmip/tests/unit/services/server/test_engine.py b/kmip/tests/unit/services/server/test_engine.py index e96e302..d6dbb64 100644 --- a/kmip/tests/unit/services/server/test_engine.py +++ b/kmip/tests/unit/services/server/test_engine.py @@ -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.