Repository: tenable/pyTenable

Issue:
Crash when pagination value is null in JSON response
**Describe the bug**

The following program crashed, since sometime the server returns JSON data which are not correctly handle by the `SearchIterator` class in `explore_iterator.py`.

**To Reproduce**
Steps to reproduce the behavior:

I'm calling the API endpoint (`/api/v3/findings/vulnerabilities/webapp/search`) on cloud.tenable.com, using `tio.v3.explore.findings.search_webapp` function with some arguments:
```json
{
    "limit": 200,
    "filter": {
        "and": [
            {
                "operator": "eq",
                "value": "redacted",
                "property": "asset_id"
            }
        ]
    },
    "fields": [
        "asset",
        "definition.id",
        "definition.vpr.score",
        "definition.exploitability_ease"
    ],
    "next": "**redacted**"
}
```
The server responds (sometimes):
```json
{"findings":[**redacted**],"pagination":null}
```

Since the python code is:
```python
def _process_response(self, response: Response) -> None:
    '''  
    Process the API Response
    '''
    body = response.json()
    pagination = body.get('pagination', {})
    self.page = body[self._resource]
    self.total = pagination.get('total')
    self._next_token = pagination.get('next')
```
The key `pagination` actually exists in JSON response, so `pagination` variable will be `None` and not a dict.
Crash happens line 8 (line 72 in the actual code). 

```
Traceback (most recent call last):
  ***redacted***
  File "/redacted/lib/python3.10/site-packages/restfly/iterator.py", line 114, in __next__
    return self.next()  # noqa: PLE1102
  File "/redacted/lib/python3.10/site-packages/restfly/iterator.py", line 140, in next
    self._get_page()
  File "/redacted/lib/python3.10/site-packages/tenable/io/v3/base/iterators/explore_iterator.py", line 59, in _get_page
    self._process_response(resp)
  File "/redacted/lib/python3.10/site-packages/tenable/io/v3/base/iterators/explore_iterator.py", line 74, in _process_response
    self.total = pagination.get('total')
AttributeError: 'NoneType' object has no attribute 'get'
```

**Expected behavior**
The code should not crashed.

**System Information (please complete the following information):**
 - OS: Linux
 - Architecture 64bit


Discussion:

Patch:
diff --git a/tenable/io/v3/base/iterators/explore_iterator.py b/tenable/io/v3/base/iterators/explore_iterator.py
index 737e03d..20c5dc2 100644
--- a/tenable/io/v3/base/iterators/explore_iterator.py
+++ b/tenable/io/v3/base/iterators/explore_iterator.py
@@ -69,7 +69,9 @@ class SearchIterator(ExploreIterator):
         Process the API Response
         '''
         body = response.json()
-        pagination = body.get('pagination', {})
+        # Pagination value can be null in JSON response, we need to make sure
+        # a dict is returned
+        pagination = body.get('pagination') or {}
         self.page = body[self._resource]
         self.total = pagination.get('total')
         self._next_token = pagination.get('next')


Test patch:
diff --git a/tests/io/test_search_iterator_v3.py b/tests/io/test_search_iterator_v3.py
index 8c063cd..8b896a5 100644
--- a/tests/io/test_search_iterator_v3.py
+++ b/tests/io/test_search_iterator_v3.py
@@ -2,6 +2,7 @@
 Testing the search iterators for V3 endpoints
 '''
 import pytest
+import json
 
 from tenable.io.v3.base.iterators.explore_iterator import SearchIterator
 
@@ -21,7 +22,6 @@ ASSET_DATA = [
 
 ]
 
-
 @pytest.mark.vcr()
 def test_search_iterator_v3(api):
     '''
@@ -61,3 +61,22 @@ def test_search_iterator_v3(api):
 
     with pytest.raises(StopIteration):
         next(search_iterator)
+
+
+def test_search_iterator_v3_null_pagination(api):
+    '''
+    Test for null pagination in SearchIterator._process_response
+    '''
+    search_iterator = SearchIterator(
+        api=api
+    )
+    class TempJson:
+        def json(self):
+            return json.loads(json.dumps({'findings': [{'id': 'abcdef'}],
+                                          'pagination': None
+                                          })
+                              )
+    search_iterator._resource = "findings"
+    search_iterator._process_response(TempJson())
+    assert search_iterator.total == None
+    assert search_iterator._next_token == None