Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-passivetotal for
openSUSE:Factory checked in at 2022-03-22 19:39:49
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-passivetotal (Old)
and /work/SRC/openSUSE:Factory/.python-passivetotal.new.25692 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-passivetotal"
Tue Mar 22 19:39:49 2022 rev:17 rq:963790 version:2.5.9
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-passivetotal/python-passivetotal.changes
2022-02-03 23:20:44.266871605 +0100
+++
/work/SRC/openSUSE:Factory/.python-passivetotal.new.25692/python-passivetotal.changes
2022-03-22 19:39:50.991067859 +0100
@@ -1,0 +2,42 @@
+Mon Mar 14 21:39:35 UTC 2022 - Sebastian Wagner <[email protected]>
+
+- - update to version 2.5.9:
+ - Enhancements:
+ - Significant improvements to the Attack Surface Intelligence (ASI)
documentation. Added
+ - class references for ASI, CTI and vulnerability intelligence to ensure the
docs and links
+ - generated properly. Introduced a new Sphinx module to help generate inline
table-of-contents
+ - for complex classes. Corrected typos in docstrings and ensured consistent
type references
+ - when methods returned RecordList-type objects.
+ - Implemented new config files for readthedocs to align with current
documentation practices.
+ - New `whois_history` property of `Hostname` and `IPAddress` entities gives
direct access
+ - to historical Whois (ownership) records. Includes more consistent
implementation of
+ - RecordList functionality and better pandas dataframe support for both
historical Whois and
+ - field-level Whois searches.
+ - New `impacted_attack_surfaces` property of vulnerability articles
(`VulnArticle`) filters
+ - the list of third-party vendors to only those with at least one
observation. The Illuminate
+ - API returns all attack surfaces associated with an API key regardless of
whether they are
+ - impacted; the complete list is still available in the `attack_surfaces`
property. Also updated
+ - the `info` view of the Pandas dataframe on a vulnerability article so the
`impacts` column
+ - shows the count of impacted attack surfaces.
+ - Bug Fixes:
+ - Correctly sum insight and observation counts when accessing Attack Surface
Insights
+(ASIs) across multiple severity levels. Previously the `active_insight_count`,
+`total_insight_count`, and `total_observations` properties of the
`all_active_insights`
+ - record list were only counting high-priority insights.
+ - Fixed issue that caused an exception when trying to generate a dictionary
view of an
+ - AttackSurfaceComponent (detection).
+ - Removed reference to non-existant field in `VulnArticle` that was causing
an exception when
+ - rendering a vulnerability article as a dictionary with the `as_dict`
property.
+ - Handle vuln articles with no impacted assets without raising an exception.
+- update to version 2.5.8:
+ - Enhancements:
+ - `certificates` property of `analyzer.Hostname` objects now returns same
list of SSL
+ - certificates as the UI, enabled by a CertificateField search with the
field set to
+`name`. This activates special-case functionality in the API that performs a
+ - substring search for a hostname across both subjectAlternativeNames and
subjectCommonName fields
+ - The previous version only looked at the `subjectAlternativeNames` field. A
more narrow
+ - search across specific fields is still available by instantiating an
+`analyzer.CertificateField` object directly.
+ - Docs now show current version number and link to this changelog hosted on
GitHub.
+
+-------------------------------------------------------------------
Old:
----
passivetotal-2.5.8.tar.gz
New:
----
passivetotal-2.5.9.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-passivetotal.spec ++++++
--- /var/tmp/diff_new_pack.pjFfYT/_old 2022-03-22 19:39:51.491068383 +0100
+++ /var/tmp/diff_new_pack.pjFfYT/_new 2022-03-22 19:39:51.495068387 +0100
@@ -19,7 +19,7 @@
%{?!python_module:%define python_module() python-%{**} python3-%{**}}
%bcond_without test
Name: python-passivetotal
-Version: 2.5.8
+Version: 2.5.9
Release: 0
Summary: Client for the PassiveTotal REST API
License: GPL-2.0-only
++++++ passivetotal-2.5.8.tar.gz -> passivetotal-2.5.9.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/passivetotal-2.5.8/CHANGELOG.md
new/passivetotal-2.5.9/CHANGELOG.md
--- old/passivetotal-2.5.8/CHANGELOG.md 2022-01-25 23:36:22.000000000 +0100
+++ new/passivetotal-2.5.9/CHANGELOG.md 2022-03-11 19:30:24.000000000 +0100
@@ -1,5 +1,40 @@
# Changelog
+## v2.5.9
+
+#### Enhancements
+
+- Significant improvements to the Attack Surface Intelligence (ASI)
documentation. Added
+class references for ASI, CTI and vulnerability intelligence to ensure the
docs and links
+generated properly. Introduced a new Sphinx module to help generate inline
table-of-contents
+for complex classes. Corrected typos in docstrings and ensured consistent type
references
+when methods returned RecordList-type objects.
+- Implemented new config files for readthedocs to align with current
documentation practices.
+- New `whois_history` property of `Hostname` and `IPAddress` entities gives
direct access
+to historical Whois (ownership) records. Includes more consistent
implementation of
+RecordList functionality and better pandas dataframe support for both
historical Whois and
+field-level Whois searches.
+- New `impacted_attack_surfaces` property of vulnerability articles
(`VulnArticle`) filters
+the list of third-party vendors to only those with at least one observation.
The Illuminate
+API returns all attack surfaces associated with an API key regardless of
whether they are
+impacted; the complete list is still available in the `attack_surfaces`
property. Also updated
+the `info` view of the Pandas dataframe on a vulnerability article so the
`impacts` column
+shows the count of impacted attack surfaces.
+
+
+#### Bug Fixes
+
+- Correctly sum insight and observation counts when accessing Attack Surface
Insights
+(ASIs) across multiple severity levels. Previously the `active_insight_count`,
+`total_insight_count`, and `total_observations` properties of the
`all_active_insights`
+record list were only counting high-priority insights.
+- Fixed issue that caused an exception when trying to generate a dictionary
view of an
+AttackSurfaceComponent (detection).
+- Removed reference to non-existant field in `VulnArticle` that was causing an
exception when
+rendering a vulnerability article as a dictionary with the `as_dict` property.
+- Handle vuln articles with no impacted assets without raising an exception.
+
+
## v2.5.8
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/passivetotal-2.5.8/PKG-INFO
new/passivetotal-2.5.9/PKG-INFO
--- old/passivetotal-2.5.8/PKG-INFO 2022-01-25 23:37:02.000000000 +0100
+++ new/passivetotal-2.5.9/PKG-INFO 2022-03-11 19:31:28.000000000 +0100
@@ -1,12 +1,12 @@
Metadata-Version: 2.1
Name: passivetotal
-Version: 2.5.8
+Version: 2.5.9
Summary: Library for the RiskIQ PassiveTotal and Illuminate API
Home-page: https://github.com/passivetotal/python_api
+Download-URL: https://github.com/passivetotal/python_api/archive/master.zip
Author: RiskIQ
Author-email: [email protected]
License: GPLv2
-Download-URL: https://github.com/passivetotal/python_api/archive/master.zip
Keywords: threats,research,analysis
Platform: UNKNOWN
Description-Content-Type: text/markdown
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/passivetotal-2.5.8/docs/analyzer.rst
new/passivetotal-2.5.9/docs/analyzer.rst
--- old/passivetotal-2.5.8/docs/analyzer.rst 2021-09-27 17:47:56.000000000
+0200
+++ new/passivetotal-2.5.9/docs/analyzer.rst 2022-03-11 19:30:24.000000000
+0100
@@ -72,8 +72,8 @@
:inherited-members:
-Module Reference
-----------------
+Analyzer Module Reference
+-------------------------
.. automodule:: passivetotal.analyzer
:members:
@@ -98,6 +98,8 @@
Whois Records
-------------
+Domain Name Whois
+^^^^^^^^^^^^^^^^^
The `whois` property for host names returns the DomainWhois record for
the registered domain name portion of the host name.
@@ -113,7 +115,50 @@
prepared for missing or malformed data. Access the `raw` record for the API
response
directly as a Python dict or use the `record` property to get the raw Whois
response.
-The RiskIQ PassiveTotal API can search Whois records by field to find related
+IP Whois Records
+^^^^^^^^^^^^^^^^
+The `whois` property is also available for IP addresses. Some of the fields
are different
+than domain whois records, but most are the same.
+
+.. code-block:: python
+
+ >>> from passivetotal import analyzer
+ >>> analyzer.init()
+ >>> print(analyzer.IPAddress('160.69.1.37').whois.organization)
+ PACCAR, Inc.
+
+
+Using Whois Fields
+^^^^^^^^^^^^^^^^^^
+Fields in the IP and Hostname Whois record are returned as `WhoisField`
objects to faciliate
+field-level searching. Cast the field as a string if you need to get the
actual value:
+
+.. code-block:: python
+
+ >>> org = analyzer.IPAddress('160.69.1.37').whois.organization
+ >>> org
+ WhoisField('organization','PACCAR, Inc.')
+ >>> print(org)
+ Paccar, Inc.
+ >>> org_str = str(org)
+ >>> org_str
+ Paccar, Inc.
+
+You can also start with a field name and a string and search for it directly
to find domains
+or IPs associated with a name, email address, or other supported field type.
+
+.. code-block:: python
+
+ >>> from passivetotal.analyzer.whois import WhoisField
+ >>> WhoisField('email','[email protected]').records.domains
+
+
+
+
+
+Search Whois Records
+^^^^^^^^^^^^^^^^^^^^
+The RiskIQ PassiveTotal API can search Whois records by field to find related
domain names with the same contact information. Use the `records` property of
supported fields (any property that returns type `WhoisField`).
@@ -123,11 +168,65 @@
>>> analyzer.Hostname('riskiq.net').whois.organization.records.domains
{Hostname('riskiq.com'), Hostname('riskiq.net'), Hostname('riskiqeg.com')}
+The `records` property returns the same type of list-like object that other
analyzer
+objects return, so you can filter, sort, and convert to pandas DataFrames as
needed.
+See below for reference.
+
+
+Historical Whois Records
+^^^^^^^^^^^^^^^^^^^^^^^^
+Historical whois records may be available for domain names and IP addresses.
Access the
+`whois_history` property of an `IPAddress` or `Hostname` object to obtain a
list of historically
+observed records as a standard analyzer `RecordList`.
+
+
+.. code-block:: python
+
+ >>> from passivetotal import analyzer
+ >>> for record in analyzer.Hostname('passivetotal.org').whois_history:
+ print(record.last_seen, record.registrant_email)
+ 2022-02-11 19:16:29.334000-08:00
[email protected]
+ 2021-04-05 09:13:10.330000-07:00
[email protected]
+ 2021-04-04 08:40:41.295000-07:00
[email protected]
+ 2021-04-03 16:25:42.455000-07:00
[email protected]
+ 2021-04-06 08:42:07.273000-07:00
[email protected]
+ 2021-03-04 15:45:41.026000-08:00
[email protected]
+ 2021-03-03 13:56:05.605000-08:00
[email protected]
+ 2020-10-06 02:41:38.359000-07:00
[email protected]
+ 2020-10-06 14:01:33.575000-07:00
[email protected]
+ 2020-08-21 04:20:43.757000-07:00
[email protected]
+ 2020-06-20 03:57:01.411000-07:00 [email protected]
+ 2020-03-16 15:22:46.043000-07:00 [email protected]
+ 2020-03-17 06:57:02.899000-07:00 [email protected]
+ 2019-09-29 07:45:02.096000-07:00 [email protected]
+
+In this example, we accessed only two fields of each record, but the complete
Whois record
+remains available. Consider using the `as_dict` or `as_df` properties of the
`whois_history`
+object to get the complete list as a Python dictionary or a Pandas dataframe.
+
+.. code-block:: python
+
+ >>> analyzer.Hostname('passivetotal.org').whois_history.as_dict
+ {'records': [{'admin': {'email':
'[email protected]'},
+ 'billing': {},
+ 'registrant': {'country': 'US',
+ 'email': '[email protected]',
+ ...
+
+
+
+Whois Object Reference
+^^^^^^^^^^^^^^^^^^^^^^
+
.. autoclass:: passivetotal.analyzer.whois.DomainWhois
:members:
:inherited-members:
+.. autoclass:: passivetotal.analyzer.whois.IPWhois
+ :members:
+ :inherited-members:
+
.. autoclass:: passivetotal.analyzer.whois.WhoisField
:members:
:inherited-members:
@@ -136,6 +235,18 @@
:members:
:inherited-members:
+.. autoclass:: passivetotal.analyzer.whois.HistoricalWhoisRecords
+ :members:
+ :inherited-members:
+
+.. autoclass:: passivetotal.analyzer.whois.HistoricalDomainWhois
+ :members:
+ :inherited-members:
+
+.. autoclass:: passivetotal.analyzer.whois.HistoricalIPWhois
+ :members:
+ :inherited-members:
+
Threat Intel Articles
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/passivetotal-2.5.8/docs/conf.py
new/passivetotal-2.5.9/docs/conf.py
--- old/passivetotal-2.5.8/docs/conf.py 2021-07-15 21:35:42.000000000 +0200
+++ new/passivetotal-2.5.9/docs/conf.py 2022-03-11 19:30:24.000000000 +0100
@@ -31,10 +31,17 @@
# ones.
extensions = [
'sphinx.ext.autodoc',
+ 'sphinx.ext.autosummary',
'sphinx.ext.intersphinx',
'sphinx.ext.todo',
'sphinx.ext.coverage',
'sphinx.ext.ifconfig',
+ 'autoclasstoc',
+]
+
+autoclasstoc_sections = [
+ 'public-attrs',
+ 'public-methods',
]
# Add any paths that contain templates here, relative to this directory.
@@ -294,7 +301,9 @@
import os
on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
-if not on_rtd: # only import and set the theme if we're building docs locally
+if on_rtd:
+ html_theme = 'default'
+else:
import sphinx_rtd_theme
html_theme = 'sphinx_rtd_theme'
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/passivetotal-2.5.8/docs/illuminate.rst
new/passivetotal-2.5.9/docs/illuminate.rst
--- old/passivetotal-2.5.8/docs/illuminate.rst 2021-09-27 17:47:56.000000000
+0200
+++ new/passivetotal-2.5.9/docs/illuminate.rst 2022-03-11 19:30:24.000000000
+0100
@@ -189,7 +189,7 @@
>>> insights = my_asi.medium_priority_insights.only_active_insights
>>> first_insight = insights[0]
- >>> for observation in first_insight:
+ >>> for observation in first_insight.observations:
print(observation.type)
print(observation.name)
HOST
@@ -289,32 +289,53 @@
ASI Reference
^^^^^^^^^^^^^
+.. autosummary::
+ :nosignatures:
+
+ passivetotal.analyzer.illuminate.asi.AttackSurfaces
+ passivetotal.analyzer.illuminate.asi.AttackSurface
+ passivetotal.analyzer.illuminate.asi.AttackSurfaceInsights
+ passivetotal.analyzer.illuminate.asi.AttackSurfaceInsight
+ passivetotal.analyzer.illuminate.asi.AttackSurfaceObservations
+ passivetotal.analyzer.illuminate.asi.AttackSurfaceObservation
+
.. autoclass:: passivetotal.analyzer.illuminate.asi.AttackSurfaces
:members:
:inherited-members:
+ .. autoclasstoc::
+
.. autoclass:: passivetotal.analyzer.illuminate.asi.AttackSurface
:members:
:inherited-members:
+ .. autoclasstoc::
+
.. autoclass:: passivetotal.analyzer.illuminate.asi.AttackSurfaceInsights
:members:
:inherited-members:
.. automethod:: __init__
+ .. autoclasstoc::
.. autoclass:: passivetotal.analyzer.illuminate.asi.AttackSurfaceInsight
:members:
:inherited-members:
+ .. autoclasstoc::
+
.. autoclass:: passivetotal.analyzer.illuminate.asi.AttackSurfaceObservations
:members:
:inherited-members:
+ .. autoclasstoc::
+
.. autoclass:: passivetotal.analyzer.illuminate.asi.AttackSurfaceObservation
:members:
:inherited-members:
+ .. autoclasstoc::
+
@@ -477,24 +498,40 @@
CTI Reference
^^^^^^^^^^^^^
+.. autosummary::
+ :nosignatures:
+
+ passivetotal.analyzer.illuminate.cti.IntelProfiles
+ passivetotal.analyzer.illuminate.cti.IntelProfile
+ passivetotal.analyzer.illuminate.cti.IntelProfileIndicatorList
+ passivetotal.analyzer.illuminate.cti.IntelProfileIndicator
+
+
.. autoclass:: passivetotal.analyzer.illuminate.cti.IntelProfiles
:members:
:inherited-members:
+ .. autoclasstoc::
+
.. autoclass:: passivetotal.analyzer.illuminate.cti.IntelProfile
:members:
:inherited-members: tuple
+ .. autoclasstoc::
+
.. autoclass:: passivetotal.analyzer.illuminate.cti.IntelProfileIndicatorList
:members:
:inherited-members:
.. automethod:: __init__
+ .. autoclasstoc::
.. autoclass:: passivetotal.analyzer.illuminate.cti.IntelProfileIndicator
:members:
:inherited-members:
+ .. autoclasstoc::
+
Vulnerability Intelligence
@@ -614,30 +651,69 @@
Vuln Reference
^^^^^^^^^^^^^^
+.. autosummary::
+ :nosignatures:
+
+ passivetotal.analyzer.illuminate.vuln.AttackSurfaceCVEs
+ passivetotal.analyzer.illuminate.vuln.AttackSurfaceCVE
+ passivetotal.analyzer.illuminate.vuln.AttackSurfaceCVEObservations
+ passivetotal.analyzer.illuminate.vuln.AttackSurfaceCVEObservation
+ passivetotal.analyzer.illuminate.vuln.AttackSurfaceComponents
+ passivetotal.analyzer.illuminate.vuln.AttackSurfaceComponent
+ passivetotal.analyzer.illuminate.vuln.VulnArticle
+ passivetotal.analyzer.illuminate.vuln.VulnArticleImpacts
+ passivetotal.analyzer.illuminate.vuln.VulnArticleImpact
+
.. autoclass:: passivetotal.analyzer.illuminate.vuln.AttackSurfaceCVEs
:members:
:inherited-members:
+ .. autoclasstoc::
+
.. autoclass:: passivetotal.analyzer.illuminate.vuln.AttackSurfaceCVE
:members:
:inherited-members:
+ .. autoclasstoc::
+
.. autoclass::
passivetotal.analyzer.illuminate.vuln.AttackSurfaceCVEObservations
:members:
:inherited-members:
+ .. autoclasstoc::
+
.. autoclass::
passivetotal.analyzer.illuminate.vuln.AttackSurfaceCVEObservation
:members:
:inherited-members:
-.. autoclass:: passivetotal.analyzer.illuminate.vuln.AttackSurfaceCVEComponents
+ .. autoclasstoc::
+
+.. autoclass:: passivetotal.analyzer.illuminate.vuln.AttackSurfaceComponents
:members:
:inherited-members:
-.. autoclass:: passivetotal.analyzer.illuminate.vuln.AttackSurfaceCVEComponent
+ .. autoclasstoc::
+
+.. autoclass:: passivetotal.analyzer.illuminate.vuln.AttackSurfaceComponent
:members:
:inherited-members:
+ .. autoclasstoc::
+
.. autoclass:: passivetotal.analyzer.illuminate.vuln.VulnArticle
:members:
- :inherited-members:
\ No newline at end of file
+ :inherited-members:
+
+ .. autoclasstoc::
+
+.. autoclass:: passivetotal.analyzer.illuminate.vuln.VulnArticleImpacts
+ :members:
+ :inherited-members:
+
+ .. autoclasstoc::
+
+.. autoclass:: passivetotal.analyzer.illuminate.vuln.VulnArticleImpact
+ :members:
+ :inherited-members:
+
+ .. autoclasstoc::
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/passivetotal-2.5.8/docs/requirements.txt
new/passivetotal-2.5.9/docs/requirements.txt
--- old/passivetotal-2.5.8/docs/requirements.txt 1970-01-01
01:00:00.000000000 +0100
+++ new/passivetotal-2.5.9/docs/requirements.txt 2022-03-11
19:30:24.000000000 +0100
@@ -0,0 +1 @@
+autoclasstoc==1.3.0
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/passivetotal-2.5.8/passivetotal/_version.py
new/passivetotal-2.5.9/passivetotal/_version.py
--- old/passivetotal-2.5.8/passivetotal/_version.py 2022-01-25
23:36:22.000000000 +0100
+++ new/passivetotal-2.5.9/passivetotal/_version.py 2022-03-11
19:30:24.000000000 +0100
@@ -1 +1 @@
-VERSION="2.5.8"
\ No newline at end of file
+VERSION="2.5.9"
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/passivetotal-2.5.8/passivetotal/analyzer/_common.py
new/passivetotal-2.5.9/passivetotal/analyzer/_common.py
--- old/passivetotal-2.5.8/passivetotal/analyzer/_common.py 2021-10-22
01:31:48.000000000 +0200
+++ new/passivetotal-2.5.9/passivetotal/analyzer/_common.py 2022-03-11
19:30:24.000000000 +0100
@@ -96,7 +96,7 @@
def as_df(self):
"""Get this object as a Pandas DataFrame.
- Use `to_dataframe()' instead if you need to control how the dataframe
is built.
+ Use `to_dataframe()` instead if you need to control how the dataframe
is built.
Requires the pandas Python library. Throws `AnalyzerError` if it is
missing.
:rtype: :class:`pandas.DataFrame`
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/passivetotal-2.5.8/passivetotal/analyzer/hostname.py
new/passivetotal-2.5.9/passivetotal/analyzer/hostname.py
--- old/passivetotal-2.5.8/passivetotal/analyzer/hostname.py 2022-01-25
23:36:22.000000000 +0100
+++ new/passivetotal-2.5.9/passivetotal/analyzer/hostname.py 2022-03-11
19:30:24.000000000 +0100
@@ -6,7 +6,7 @@
from passivetotal.analyzer._common import is_ip, refang, AnalyzerError
from passivetotal.analyzer.pdns import HasResolutions
from passivetotal.analyzer.summary import HostnameSummary, HasSummary
-from passivetotal.analyzer.whois import DomainWhois
+from passivetotal.analyzer.whois import DomainWhois, HistoricalWhoisRecords
from passivetotal.analyzer.ssl import CertificateField
from passivetotal.analyzer.hostpairs import HasHostpairs
from passivetotal.analyzer.cookies import HasCookies
@@ -96,6 +96,13 @@
self._whois = DomainWhois(response)
return self._whois
+ def _api_get_whois_history(self):
+ """Query the Whois API for historical records on this hostname."""
+ response = get_api('Whois').get_whois_details(query=self._hostname,
compact_record=False, history=True)
+ self._whois_history = HistoricalWhoisRecords()
+ self._whois_history.parse(response)
+ return self._whois_history
+
def _query_dns(self):
"""Perform a DNS lookup."""
try:
@@ -195,6 +202,16 @@
)
@property
+ def whois_history(self):
+ """Historical Whois records for this hostname.
+
+ :rtype: :class:`passivetotal.analyzer.whois.HistoricalWhoisRecords`
+ """
+ if getattr(self, '_whois_history', None) is not None:
+ return self._whois_history
+ return self._api_get_whois_history()
+
+ @property
def is_ip(self):
"""Whether this object is an IP. Always returns false.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/passivetotal-2.5.8/passivetotal/analyzer/illuminate/asi.py
new/passivetotal-2.5.9/passivetotal/analyzer/illuminate/asi.py
--- old/passivetotal-2.5.8/passivetotal/analyzer/illuminate/asi.py
2021-10-22 01:22:41.000000000 +0200
+++ new/passivetotal-2.5.9/passivetotal/analyzer/illuminate/asi.py
2022-03-11 19:30:24.000000000 +0100
@@ -17,7 +17,8 @@
class AttackSurfaces(RecordList, PagedRecordList, ForPandas):
- """List of RiskIQ Illuminate Attack Surfaces.
+ """Collection of RiskIQ Illuminate Attack Surfaces in a list-like object
+ containing :class:`AttackSurface` objects.
Primarily used for enumerating a set of third-party vendors.
"""
@@ -157,7 +158,7 @@
"""Get insights at a level (high, medium or low).
:param level: Priority level (high, medium, or low).
- :returns: List of :class:`AttackSurfaceInsights`
+ :returns: :class:`AttackSurfaceInsights`
"""
self._ensure_valid_level(level)
if self._insights[level] is not None:
@@ -178,6 +179,7 @@
"""Get a list of CVEs impacting assets in this attack surface.
:param pagesize: Size of pages to retrieve from the API.
+ :rtype:
:class:`passivetotal.analyzer.illuminate.vuln.AttackSurfaceCVEs`
"""
from passivetotal.analyzer.illuminate import AttackSurfaceCVEs
self._cves = AttackSurfaceCVEs(self, pagesize)
@@ -188,6 +190,8 @@
"""Get a list of vulnerable components (detections) in this attack
surface.
:param pagesize: Size of pages to retrieve from the API.
+
+ :rtype:
:class:`passivetotal.analyzer.illuminate.vuln.AttackSurfaceComponents`
"""
from passivetotal.analyzer.illuminate import AttackSurfaceComponents
self._components = AttackSurfaceComponents(self, pagesize)
@@ -214,12 +218,17 @@
:rtype:
:class:`passivetotal.analyzer.illuminate.asi.AttackSurfaceInsights`
"""
- insights = self.high_priority_insights._make_shallow_copy()
- insights._level = 'ALL'
- insights._records = []
- for level in self._LEVELS:
- insights._records.extend(self.get_insights(level)._records)
- return insights
+ insights_all = self.high_priority_insights._make_shallow_copy()
+ count_fields =
['_count_active_insights','_count_total_insights','_count_total_observations']
+ for field in count_fields:
+ setattr(insights_all, field, 0)
+ insights_all._level = 'ALL'
+ insights_all._records = []
+ for level in [self.get_insights(level) for level in self._LEVELS]:
+ insights_all._records.extend(level._records)
+ for field in count_fields:
+ setattr(insights_all, field, getattr(insights_all, field) +
getattr(level, field))
+ return insights_all
@property
def all_active_insights(self):
@@ -266,7 +275,7 @@
def high_priority_insights(self):
"""Get high priority insights.
- :rtype: List of
:class:`passivetotal.analyzer.illuminate.asi.AttackSurfaceInsights`
+ :rtype:
:class:`passivetotal.analyzer.illuminate.asi.AttackSurfaceInsights`
"""
return self.get_insights('high')
@@ -274,7 +283,7 @@
def medium_priority_insights(self):
"""Get medium priority insights.
- :rtype: List of
:class:`passivetotal.analyzer.illuminate.asi.AttackSurfaceInsights`
+ :rtype:
:class:`passivetotal.analyzer.illuminate.asi.AttackSurfaceInsights`
"""
return self.get_insights('medium')
@@ -282,7 +291,7 @@
def low_priority_insights(self):
"""Get low priority insights.
- :rtype: List of
:class:`passivetotal.analyzer.illuminate.asi.AttackSurfaceInsights`
+ :rtype:
:class:`passivetotal.analyzer.illuminate.asi.AttackSurfaceInsights`
"""
return self.get_insights('low')
@@ -290,7 +299,7 @@
def cves(self):
"""Get a list of CVEs associated with this attack surface.
- :rtype: List of
:class:`passivetotal.analyzer.illuminate.vuln.AttackSurfaceCVE`
+ :rtype:
:class:`passivetotal.analyzer.illuminate.vuln.AttackSurfaceCVEs`
"""
if getattr(self, '_cves', None) is not None:
return self._cves
@@ -298,7 +307,10 @@
@property
def components(self):
- """List of components (detections) vulnerable to this CVE in this
attack surface."""
+ """List of components (detections) vulnerable to this CVE in this
attack surface.
+
+ :rtype:
:class:`passivetotal.analyzer.illuminate.vuln.AttackSurfaceComponents`
+ """
if getattr(self, '_components', None) is not None:
return self._components
return self.get_components()
@@ -306,7 +318,8 @@
class AttackSurfaceInsights(RecordList, ForPandas):
- """List of insights associated with an attack surface."""
+ """Collection of insights associated with an attack surface in a list-like
object
+ containing :class:`AttackSurfaceInsight` objects."""
def __init__(self, attack_surface=None, level=None, api_response=None):
self._attack_surface = attack_surface
@@ -345,7 +358,7 @@
@property
def attack_surface(self):
- """Attach surface these insights are associated with.
+ """Attack surface these insights are associated with.
:rtype: :class:`passivetotal.analyzer.illuminate.asi.AttackSurface`
"""
@@ -363,7 +376,7 @@
def only_active_insights(self):
"""Filter to only active insights (insights with active observations).
- :rtype: bool
+ :rtype: :class:`AttackSurfaceInsights`
"""
return self.filter(has_observations=True)
@@ -466,6 +479,7 @@
"""Get a list of impacted assets (observations).
:param pagesize: Size of pages to retrieve from the API.
+ :rtype: :class:`AttackSurfaceObservations`
"""
self._observations = AttackSurfaceObservations(self, self._group_by,
self._segment_by)
self._observations.load_all_pages()
@@ -473,7 +487,10 @@
@property
def observations(self):
- """List of impacted assets."""
+ """List of impacted assets.
+
+ :rtype: :class:`AttackSurfaceObservations`
+ """
if getattr(self, '_observations', None) is not None:
return self._observations
return self.get_observations()
@@ -482,7 +499,8 @@
class AttackSurfaceObservations(RecordList, PagedRecordList, ForPandas):
- """List of observations (assets) associated with an attack surface
insight."""
+ """Collection of observations (assets) associated with an attack surface
insight
+ in a list-like object containing :class:`AttackSurfaceObservation`
objects."""
def __init__(self, insight, group_by, segment_by, pagesize=400):
self._totalrecords = None
@@ -548,6 +566,8 @@
class AttackSurfaceObservation(Record, FirstLastSeen, ForPandas):
+ """An attack surface asset (typically identified by an IP address or
hostname) that
+ is impacted by an observation."""
def __init__(self, insight, api_response):
self._insight = insight
@@ -579,18 +599,29 @@
@property
def type(self):
+ """Type of impacted asset (i.e. HOST or IP_ADDRESS)."""
return self._type
@property
def name(self):
+ """Name of impacted asset (i.e. the IP address or hostname)."""
return self._name
@property
def insight(self):
+ """Attack surface insight associated with this observation.
+
+ :rtype: :class:`AttackSurfaceInsight`
+ """
return self._insight
@property
def hostname(self):
+ """Shortcut property to access an `analyzer.Hostname` object from this
+ observation, if the observation type is HOST, else returns `None`.
+
+ :rtype: :class:`passivetotal.analyzer.Hostname`
+ """
if self._type != 'HOST':
return None
try:
@@ -600,6 +631,11 @@
@property
def ip(self):
+ """Shortcut property to access an `analyzer.IPAddress` object from this
+ observation, if the observation type is IP_ADDRESS, else returns
`None`.
+
+ :rtype: :class:`passivetotal.analyzer.IPAddress`
+ """
if self._type != 'IP_ADDRESS':
return None
try:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/passivetotal-2.5.8/passivetotal/analyzer/illuminate/cti.py
new/passivetotal-2.5.9/passivetotal/analyzer/illuminate/cti.py
--- old/passivetotal-2.5.8/passivetotal/analyzer/illuminate/cti.py
2021-09-27 17:47:56.000000000 +0200
+++ new/passivetotal-2.5.9/passivetotal/analyzer/illuminate/cti.py
2022-03-11 19:30:24.000000000 +0100
@@ -212,7 +212,7 @@
def indicators(self):
"""Unfiltered indicator list associated with this intel profile.
- Calls `passivetotal.analyzer.illuminate.IntelProfile.get_indicators()'
+ Calls `passivetotal.analyzer.illuminate.IntelProfile.get_indicators()`
with default parameters. Use that method directly for more granular
control.
:rtype:
:class:`passivetotal.analyzer.illuminate.cti.IntelProfileIndicatorList`
@@ -245,7 +245,8 @@
class IntelProfileIndicatorList(RecordList, PagedRecordList, ForPandas):
def __init__(self, profile_id=None, query=None, types=None,
categories=None, sources=None, pagesize=INDICATOR_PAGE_SIZE):
- """List of indicators associated with a RiskIQ Intel Profile.
+ """Collection of indicators associated with a RiskIQ Intel Profile as
a list-like object
+ containing :class:`IntelProfileIndicator` objects.
:param profile_id: Threat intel profile ID to search for.
:param query: Indicator value to query for.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/passivetotal-2.5.8/passivetotal/analyzer/illuminate/vuln.py
new/passivetotal-2.5.9/passivetotal/analyzer/illuminate/vuln.py
--- old/passivetotal-2.5.8/passivetotal/analyzer/illuminate/vuln.py
2021-10-22 01:31:48.000000000 +0200
+++ new/passivetotal-2.5.9/passivetotal/analyzer/illuminate/vuln.py
2022-03-11 19:30:24.000000000 +0100
@@ -15,7 +15,8 @@
class AttackSurfaceCVEs(RecordList, PagedRecordList, ForPandas):
- """List of CVEs associated with an attack surface."""
+ """Collection of CVEs associated with an attack surface as a list-like
object
+ containing :class:`AttackSurfaceCVE` objects."""
def __init__(self, attack_surface=None, pagesize=400):
self._totalrecords = None
@@ -107,6 +108,7 @@
"""Get a list of observations(assets) vulnerable to this CVE in this
attack surface.
:param pagesize: Size of pages to retrieve from the API.
+ :rtype: :class:`AttackSurfaceCVEObservations`
"""
self._observations = AttackSurfaceCVEObservations(self, pagesize)
self._observations.load_all_pages()
@@ -185,7 +187,8 @@
class AttackSurfaceCVEObservations(RecordList, PagedRecordList, ForPandas):
- """List of observations (assets) associated with a CVE in a specific
attack surface."""
+ """Collection of observations (assets) associated with a CVE in a specific
attack surface
+ as a list-like object containing :class:`AttackSurfaceCVEObservation`
objects."""
def __init__(self, cve=None, pagesize=400):
self._totalrecords = None
@@ -209,8 +212,11 @@
self._totalrecords = api_response.get('totalCount')
if self._pagination_current_page == 0:
self._records = []
- for result in api_response.get('assets',[]):
- self._records.append(AttackSurfaceCVEObservation(self._cve,
result))
+ try:
+ for result in api_response.get('assets',[]):
+ self._records.append(AttackSurfaceCVEObservation(self._cve,
result))
+ except TypeError:
+ pass # assets may be blank instead of an empty list
@property
def cve(self):
@@ -309,7 +315,8 @@
class AttackSurfaceComponents(RecordList, PagedRecordList, ForPandas):
- """List of vulnerable components (detections) associated with an attack
surface."""
+ """Collection of vulnerable components (detections) associated with an
attack surface
+ as a list-like object containing :class:`AttackSurfaceComponent`
objects."""
def __init__(self, attack_surface=None, pagesize=400):
self._totalrecords = None
@@ -338,7 +345,7 @@
@property
def attack_surface(self):
- """Get the CVE associated with this list of observations.
+ """Get the attack surface associated with this list of observations.
:rtype: :class:`passivetotal.analyzer.illuminate.asi.AttackSurface`
"""
@@ -377,7 +384,7 @@
return '<AttackSurfaceComponent {0.type}:{0.name}>'.format(self)
def _get_dict_fields(self):
- return ['vendor_id','type','name','severity','count']
+ return ['type','name','severity','count']
def to_dataframe(self):
"""Render this object as a Pandas dataframe.
@@ -477,7 +484,7 @@
def _get_dict_fields(self):
return
['id','description','cwes','score','cvss2score','cvss3score','str:date_published',
-
'str:date_updated','str:date_publisher_updated','references','components',
+ 'str:date_publisher_updated','references','components',
'observation_count']
def to_dataframe(self, view='info'):
@@ -511,7 +518,7 @@
'observations': self.observation_count,
'references': len(self.references),
'components': len(self.components),
- 'impacts': len(self._impacted3p)
+ 'impacts': len(self.impacted_attack_surfaces)
}],
'references': [{
'cve_id': self.id,
@@ -636,13 +643,26 @@
return VulnArticleImpacts(self, self._impacted3p)
@property
+ @lru_cache(maxsize=None)
+ def impacted_attack_surfaces(self):
+ """List of all attack surfaces associated with your API account that
are impacted
+ by this vulnerability (they have at least one observation of an
impacted asset).
+
+ :rtype:
:class:`passivetotal.analyzer.illuminate.vuln.VulnArticleImpacts`
+ """
+ return self.attack_surfaces.only_impacted
+
+ @property
def observation_count(self):
"""Number of observations (assets) within the primary attack surface
that are impacted by this vulnerability."""
return self._observation_count
@property
def observations(self):
- """List of observations (assets) within the primary attack surface
that are impacted by this vulnerability."""
+ """List of observations (assets) within the primary attack surface
that are impacted by this vulnerability.
+
+ :rtype: :class:`AttackSurfaceCVEObservations`
+ """
from . import AttackSurface
attack_surface = AttackSurface.load()
article = {
@@ -659,7 +679,8 @@
class VulnArticleImpacts(RecordList, ForPandas):
- """List of Illuminate Attack Surfaces impacted by a vulnerability."""
+ """Collection of Illuminate Attack Surfaces potentially impacted by a
vulnerability
+ as a list-like object containing :class:`VulnArticleImpact` objects."""
def __init__(self, article=None, impacts=[]):
self._records = []
@@ -672,7 +693,7 @@
return '<VulnArticleImpacts {0.article.id}>'.format(self)
def __str__(self):
- return '{0.article.id} impacts {0.impact_count:,} attack
surfaces(s)'.format(self)
+ return '{0.article.id} impacts {0.impact_count:,} of {0.length:,}
attack surfaces(s)'.format(self)
def _get_dict_fields(self):
return ['cve_id', 'impact_count']
@@ -685,14 +706,24 @@
@property
def article(self):
- """Article that describes the vulnerability impacting these attack
surfaces."""
+ """Article that describes the vulnerability impacting these attack
surfaces.
+
+ :rtype: :class:`VulnArticle`
+ """
return self._article
@property
def attack_surfaces(self):
- """List of impacted attack surfaces.
+ """Unfiltered list of all attack surfaces associated with your API
account.
+
+ This is the same list you would get if you iterated through an
instance of this
+ object. Each record will include a count of assets impacted by a
vulnerability, in the
+ "observation_count" property. The count may be zero.
+
+ If you need a list of only impacted attack surfaces, use the
`only_impacted`
+ property instead.
- :rtypte:
:class:`passivetotal.analyzer.illuminate.vuln.VulnArticleImpact`
+ :rtype:
:class:`passivetotal.analyzer.illuminate.vuln.VulnArticleImpacts`
"""
return self._records
@@ -702,9 +733,20 @@
return self.article.id
@property
+ def only_impacted(self):
+ """Filter the list to include only articles with at least one
observation (asset).
+
+ :rtype: :class:`VulnArticleImpacts`
+ """
+ return self.filter_fn(lambda asi: asi.observation_count > 0)
+
+ @property
def impact_count(self):
- """Number of attack surfaces impacted by this vulnerability."""
- return len(self._records)
+ """Number of attack surfaces with assets impacted by this
vulnerability.
+
+ Counts the number of records that have more than zero observations.
+ """
+ return len(self.only_impacted)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/passivetotal-2.5.8/passivetotal/analyzer/ip.py
new/passivetotal-2.5.9/passivetotal/analyzer/ip.py
--- old/passivetotal-2.5.8/passivetotal/analyzer/ip.py 2021-08-19
02:49:22.000000000 +0200
+++ new/passivetotal-2.5.9/passivetotal/analyzer/ip.py 2022-03-11
19:30:24.000000000 +0100
@@ -3,7 +3,7 @@
from passivetotal.analyzer import get_api, get_config
from passivetotal.analyzer._common import is_ip, refang, AnalyzerError
-from passivetotal.analyzer.whois import IPWhois
+from passivetotal.analyzer.whois import IPWhois, HistoricalWhoisRecords
from passivetotal.analyzer.pdns import HasResolutions
from passivetotal.analyzer.services import Services
from passivetotal.analyzer.ssl import Certificates
@@ -103,11 +103,18 @@
return self._ssl_history
def _api_get_whois(self):
- """Query the pDNS API for resolution history."""
+ """Query the whois API."""
response = get_api('Whois').get_whois_details(query=self._ip)
self._whois = IPWhois(response)
return self._whois
+ def _api_get_whois_history(self):
+ """Query the whois API for ownership history."""
+ response = get_api('Whois').get_whois_details(query=self._ip,
history=True)
+ self._whois_history = HistoricalWhoisRecords()
+ self._whois_history.parse(response)
+ return self._whois_history
+
@property
def ip(self):
"""IP address as a string."""
@@ -145,6 +152,16 @@
return self._api_get_whois()
@property
+ def whois_history(self):
+ """Historical Whois (ownership) records for this IP.
+
+ :rtype: :class:`passivetotal.analyzer.whois.HistoricalWhoisRecords`
+ """
+ if getattr(self, '_whois_history', None) is not None:
+ return self._whois_history
+ return self._api_get_whois_history()
+
+ @property
def is_ip(self):
"""Whether this object is an IP. Always returns true.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/passivetotal-2.5.8/passivetotal/analyzer/ssl.py
new/passivetotal-2.5.9/passivetotal/analyzer/ssl.py
--- old/passivetotal-2.5.8/passivetotal/analyzer/ssl.py 2021-06-29
20:04:38.000000000 +0200
+++ new/passivetotal-2.5.9/passivetotal/analyzer/ssl.py 2022-03-11
19:30:24.000000000 +0100
@@ -44,7 +44,7 @@
@property
def not_expired(self):
- """Filtered list of :class:`Certificates' that have not expired."""
+ """Filtered list of :class:`Certificates` that have not expired."""
return self.filter(expired=False)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/passivetotal-2.5.8/passivetotal/analyzer/whois.py
new/passivetotal-2.5.9/passivetotal/analyzer/whois.py
--- old/passivetotal-2.5.8/passivetotal/analyzer/whois.py 2021-06-21
16:37:13.000000000 +0200
+++ new/passivetotal-2.5.9/passivetotal/analyzer/whois.py 2022-03-11
19:30:24.000000000 +0100
@@ -2,7 +2,7 @@
from collections import namedtuple, OrderedDict
import pprint
from passivetotal.analyzer import get_api, get_object
-from passivetotal.analyzer._common import RecordList, ForPandas
+from passivetotal.analyzer._common import is_ip, Record, RecordList, ForPandas
@@ -44,6 +44,14 @@
def __repr__(self):
return "WhoisField('{0.name}','{0.value}')".format(self)
+
+ def __eq__(self, other):
+ if isinstance(other, str):
+ return self._value == other
+ return other is self
+
+ def __hash__(self):
+ return self._value.__hash__()
def _api_search(self):
"""Use the 'Whois' request wrapper to perform a keyword search by
field."""
@@ -70,7 +78,7 @@
-class WhoisRecords(RecordList):
+class WhoisRecords(RecordList, ForPandas):
"""List of Whois records."""
@@ -78,11 +86,13 @@
return []
def _get_sortable_fields(self):
- return ['domain']
+ return ['host']
def parse(self, api_response):
"""Parse an API response into a list of `DomainWhois` records."""
- self._records = list(map(DomainWhois, api_response.get('results',[])))
+ self._records = [
+ IPWhois(r) if is_ip(r['domain']) else DomainWhois(r) for r in
api_response.get('results',[])
+ ]
@property
def domains(self):
@@ -103,10 +113,25 @@
def orgs(self):
"""Return a set of unique org names in this record list."""
return set([r.organization for r in self if r.organization])
-
-class WhoisRecord(ForPandas):
+
+class HistoricalWhoisRecords(WhoisRecords):
+
+ """List of :class:`HistoricalDomainWhois` or :class:`HistoricalIPWhois`
records."""
+
+ def _get_sortable_fields(self):
+ return ['str:host','last_seen']
+
+ def parse(self, api_response):
+ """Parse an API response into a list of `HistoricalDomainWhois` or
`HistoricalIPWhois` records."""
+ self._records = [
+ HistoricalIPWhois(r) if is_ip(r['domain']) else
HistoricalDomainWhois(r) for r in api_response.get('results',[])
+ ]
+
+
+
+class WhoisRecord(Record, ForPandas):
"""Base type for IP and Domain Whois."""
def _get_contacts(self, contact_type):
@@ -339,6 +364,12 @@
"""Raw API response."""
return self._rawrecord
+ @property
+ def host(self):
+ """Get the IP or domain name this record is associated with, as an
+ :class:`analyzer.IPAddress` or :class:`analyzer.Hostname` object."""
+ return get_object(self._domain, type = 'IPAddress' if self.is_ip else
'Hostname')
+
class DomainWhois(WhoisRecord):
@@ -386,6 +417,46 @@
:rtype: datetime
"""
return self._parsedate('expiresAt')
+
+ @property
+ def is_domain(self):
+ return True
+
+ @property
+ def is_ip(self):
+ return False
+
+
+
+class HistoricalDomainWhois(DomainWhois):
+
+ """Historical Whois record for a domain name."""
+
+ def __new__(cls, record):
+ self = object.__new__(HistoricalDomainWhois)
+ self._domain = record['domain']
+ self._rawrecord = record
+ return self
+
+ def __str__(self):
+ return '{0.last_seen} registrant: "{0.organization} |
{0.registrant_name} | {0.registrant_email}"'.format(self)
+
+ def __repr__(self):
+ return "HistoricalDomainWhois<'{0.domain}' @
'{0.last_seen}'>".format(self)
+
+ def _dict_for_df(self, **kwargs):
+ as_d = OrderedDict(last_seen=self.last_seen)
+ as_d.update(super()._dict_for_df(**kwargs))
+ return as_d
+
+ @property
+ def last_seen(self):
+ """Date the historical record was last seen.
+
+ Alias for `date_loaded`.
+ :rtype: datetime
+ """
+ return self.date_loaded
@@ -418,3 +489,42 @@
def ip(self):
return get_object(self._domain, type='IPAddress')
+ @property
+ def is_domain(self):
+ return False
+
+ @property
+ def is_ip(self):
+ return True
+
+
+
+class HistoricalIPWhois(IPWhois):
+
+ """Historical Whois record for a IP address."""
+
+ def __new__(cls, record):
+ self = object.__new__(HistoricalIPWhois)
+ self._domain = record['domain']
+ self._rawrecord = record
+ return self
+
+ def __str__(self):
+ return '{0.last_seen} registrant: "{0.organization} |
{0.registrant_name} | {0.registrant_email}"'.format(self)
+
+ def __repr__(self):
+ return "HistoricalIPWhois<'{0.ip}' @ '{0.last_seen}'>".format(self)
+
+ def _dict_for_df(self, **kwargs):
+ as_d = OrderedDict(last_seen=self.last_seen)
+ as_d.update(super()._dict_for_df(**kwargs))
+ return as_d
+
+ @property
+ def last_seen(self):
+ """Date the historical record was last seen.
+
+ Alias for `date_loaded`.
+ :rtype: datetime
+ """
+ return self.date_loaded
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/passivetotal-2.5.8/passivetotal.egg-info/PKG-INFO
new/passivetotal-2.5.9/passivetotal.egg-info/PKG-INFO
--- old/passivetotal-2.5.8/passivetotal.egg-info/PKG-INFO 2022-01-25
23:37:02.000000000 +0100
+++ new/passivetotal-2.5.9/passivetotal.egg-info/PKG-INFO 2022-03-11
19:31:27.000000000 +0100
@@ -1,12 +1,12 @@
Metadata-Version: 2.1
Name: passivetotal
-Version: 2.5.8
+Version: 2.5.9
Summary: Library for the RiskIQ PassiveTotal and Illuminate API
Home-page: https://github.com/passivetotal/python_api
+Download-URL: https://github.com/passivetotal/python_api/archive/master.zip
Author: RiskIQ
Author-email: [email protected]
License: GPLv2
-Download-URL: https://github.com/passivetotal/python_api/archive/master.zip
Keywords: threats,research,analysis
Platform: UNKNOWN
Description-Content-Type: text/markdown
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/passivetotal-2.5.8/passivetotal.egg-info/SOURCES.txt
new/passivetotal-2.5.9/passivetotal.egg-info/SOURCES.txt
--- old/passivetotal-2.5.8/passivetotal.egg-info/SOURCES.txt 2022-01-25
23:37:02.000000000 +0100
+++ new/passivetotal-2.5.9/passivetotal.egg-info/SOURCES.txt 2022-03-11
19:31:28.000000000 +0100
@@ -10,6 +10,7 @@
docs/getting-started.rst
docs/illuminate.rst
docs/index.rst
+docs/requirements.txt
docs/wrappers.rst
examples/notebooks/Attack Surface & Vulnerability Intelligence - RiskIQ
API.ipynb
examples/notebooks/Cyber Threat Intelligence (CTI) - RiskIQ API.ipynb
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/passivetotal-2.5.8/passivetotal.egg-info/entry_points.txt
new/passivetotal-2.5.9/passivetotal.egg-info/entry_points.txt
--- old/passivetotal-2.5.8/passivetotal.egg-info/entry_points.txt
2022-01-25 23:37:02.000000000 +0100
+++ new/passivetotal-2.5.9/passivetotal.egg-info/entry_points.txt
2022-03-11 19:31:28.000000000 +0100
@@ -2,4 +2,3 @@
pt-client = passivetotal.cli.client:main
pt-config = passivetotal.cli.config:main
pt-info = passivetotal.cli.info:main
-