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 2021-08-04 22:28:58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-passivetotal (Old) and /work/SRC/openSUSE:Factory/.python-passivetotal.new.1899 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-passivetotal" Wed Aug 4 22:28:58 2021 rev:11 rq:910121 version:2.5.3 Changes: -------- --- /work/SRC/openSUSE:Factory/python-passivetotal/python-passivetotal.changes 2021-07-10 22:55:24.555338646 +0200 +++ /work/SRC/openSUSE:Factory/.python-passivetotal.new.1899/python-passivetotal.changes 2021-08-04 22:29:40.941743618 +0200 @@ -1,0 +2,28 @@ +Wed Jul 28 10:11:21 UTC 2021 - Sebastian Wagner <sebix+novell....@sebix.at> + +- update to version 2.5.3: + - Enhancements: + - Better support for unit tests in client libraries with ability to set a + session to override default request methods. + - Add flexibility to library class instantiation to prefer keyword parameters + over config file keys. + - Support for new `create_date` Articles API data field and query parameter. Enables + searching for most recent articles instead of returning all of them at once, and + provides visiblity to situations where an article published in the past was recently + added to the Articles collection. + - Breaking Changes: + - Previously, calls to `analyzer.AllArticles()` would return all articles without a date + limit. Now, it will return only articles created after the starting date set with + `analyzer.set_date_range()`. The current module-level default for all date-bounded queries + is 90 days back, so now this function will return all articles created in the last 90 days. + - `age` property of an Article analyzer object is now based on `create_date` instead of publish + date. +- update to version 2.5.2: + - Enhancements: + - Send new request headers for metrics and troubleshooting with the `set_context` + method on the `analyzer` module and within the core API request libs. + - Abstract package version into a distinct file to consolidate updates and ensure + consistency across docs and pypi. Add `get_version` method to `analyzer` module + for easy access to the current version number. + +------------------------------------------------------------------- Old: ---- passivetotal-2.5.1.tar.gz New: ---- passivetotal-2.5.3.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-passivetotal.spec ++++++ --- /var/tmp/diff_new_pack.fsXwL4/_old 2021-08-04 22:29:41.377743087 +0200 +++ /var/tmp/diff_new_pack.fsXwL4/_new 2021-08-04 22:29:41.381743082 +0200 @@ -19,7 +19,7 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} %bcond_without test Name: python-passivetotal -Version: 2.5.1 +Version: 2.5.3 Release: 0 Summary: Client for the PassiveTotal REST API License: GPL-2.0-only ++++++ passivetotal-2.5.1.tar.gz -> passivetotal-2.5.3.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/passivetotal-2.5.1/CHANGELOG.md new/passivetotal-2.5.3/CHANGELOG.md --- old/passivetotal-2.5.1/CHANGELOG.md 2021-06-29 20:04:38.000000000 +0200 +++ new/passivetotal-2.5.3/CHANGELOG.md 2021-07-28 01:47:09.000000000 +0200 @@ -1,5 +1,51 @@ # Changelog +## v2.5.3 + +#### Enhancements + +- Better support for unit tests in client libraries with ability to set a + session to override default request methods. +- Add flexibility to library class instantiation to prefer keyword parameters + over config file keys. +- Support for new `create_date` Articles API data field and query parameter. Enables + searching for most recent articles instead of returning all of them at once, and + provides visiblity to situations where an article published in the past was recently + added to the Articles collection. + + +#### Breaking Changes + +- Previously, calls to `analyzer.AllArticles()` would return all articles without a date + limit. Now, it will return only articles created after the starting date set with + `analyzer.set_date_range()`. The current module-level default for all date-bounded queries + is 90 days back, so now this function will return all articles created in the last 90 days. +- `age` property of an Article analyzer object is now based on `create_date` instead of publish + date. + + +#### Bug Fixes + +[ none ] + + + +## v2.5.2 + +#### Enhancements + +- Send new request headers for metrics and troubleshooting with the `set_context` + method on the `analyzer` module and within the core API request libs. +- Abstract package version into a distinct file to consolidate updates and ensure + consistency across docs and pypi. Add `get_version` method to `analyzer` module + for easy access to the current version number. + + +#### Bug Fixes + + + + ## v2.5.1 #### Enhancements diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/passivetotal-2.5.1/PKG-INFO new/passivetotal-2.5.3/PKG-INFO --- old/passivetotal-2.5.1/PKG-INFO 2021-06-29 20:04:56.000000000 +0200 +++ new/passivetotal-2.5.3/PKG-INFO 2021-07-28 01:48:06.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: passivetotal -Version: 2.5.1 +Version: 2.5.3 Summary: Library for the RiskIQ PassiveTotal and Illuminate API Home-page: https://github.com/passivetotal/python_api Author: RiskIQ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/passivetotal-2.5.1/docs/conf.py new/passivetotal-2.5.3/docs/conf.py --- old/passivetotal-2.5.1/docs/conf.py 2021-06-29 20:04:38.000000000 +0200 +++ new/passivetotal-2.5.3/docs/conf.py 2021-07-15 21:35:42.000000000 +0200 @@ -14,6 +14,7 @@ import sys import os +import re # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the @@ -55,14 +56,12 @@ copyright = u'2021, RiskIQ' author = u'RiskIQ' -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = '2.5' -# The full version, including alpha/beta/rc tags. -release = '2.5.1' +# pylint: disable=locally-disabled, invalid-name +with open('../passivetotal/_version.py', 'r') as fd: + v_match = re.search(r'^VERSION\s*=\s*[\'"]([^\'"]*)[\'"]', fd.read(), re.MULTILINE) + release = v_match.group(1) + version = '.'.join(release.split('.')[0:-1]) +# pylint: enable=locally-disabled, invalid-name # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/passivetotal-2.5.1/passivetotal/_version.py new/passivetotal-2.5.3/passivetotal/_version.py --- old/passivetotal-2.5.1/passivetotal/_version.py 1970-01-01 01:00:00.000000000 +0100 +++ new/passivetotal-2.5.3/passivetotal/_version.py 2021-07-28 01:47:09.000000000 +0200 @@ -0,0 +1 @@ +VERSION="2.5.3" \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/passivetotal-2.5.1/passivetotal/analyzer/__init__.py new/passivetotal-2.5.3/passivetotal/analyzer/__init__.py --- old/passivetotal-2.5.1/passivetotal/analyzer/__init__.py 2021-06-21 16:37:13.000000000 +0200 +++ new/passivetotal-2.5.3/passivetotal/analyzer/__init__.py 2021-07-28 01:47:09.000000000 +0200 @@ -3,6 +3,8 @@ from collections import namedtuple from datetime import datetime, timezone, timedelta from passivetotal import * +from passivetotal._version import VERSION +from passivetotal.api import Context from passivetotal.analyzer._common import AnalyzerError, AnalyzerAPIError, is_ip DEFAULT_DAYS_BACK = 90 @@ -54,8 +56,9 @@ if 'username' in kwargs and 'api_key' in kwargs: api_clients[name] = c(**kwargs) else: - api_clients[name] = c.from_config() + api_clients[name] = c.from_config(**kwargs) api_clients[name].exception_class = AnalyzerAPIError + api_clients[name].set_context('python','passivetotal',VERSION,'analyzer') config['is_ready'] = True def get_api(name): @@ -91,6 +94,23 @@ raise AnalyzerError('type must be IPAddress or Hostname') return objs[type](input) +def get_version(): + """Get the current version of this package.""" + return VERSION + +def set_context(provider, variant, version, feature=''): + """Define the application context for an implementation using the analyzer module. + + Sets a header to be sent in API requests that is used for metrics and troubleshooting. + + :param provider: The company, partner, provider or other top-level application context. + :param variant: The specific app, libary subcomponent, or feature category. + :param version: Version of the app, feature or code setting the context. + :param feature: Optional sub-feature, dashboard or script name. + """ + for client in api_clients.values(): + client.set_context(provider, variant, version, feature) + def set_date_range(days_back=DEFAULT_DAYS_BACK, start=None, start_date=None, end=None, end_date=None): """Set a range of dates for all date-bounded API queries. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/passivetotal-2.5.1/passivetotal/analyzer/_common.py new/passivetotal-2.5.3/passivetotal/analyzer/_common.py --- old/passivetotal-2.5.1/passivetotal/analyzer/_common.py 2021-06-29 20:04:38.000000000 +0200 +++ new/passivetotal-2.5.3/passivetotal/analyzer/_common.py 2021-07-28 01:47:09.000000000 +0200 @@ -429,7 +429,13 @@ self.json = self.response.json() except Exception: self.json = {} - self.message = self.json.get('error', self.json.get('message', str(response))) + if self.json is None: + self.message = 'No JSON data in API response' + else: + try: + self.message = self.json.get('error', self.json.get('message', str(response))) + except Exception: + self.message = '' def __str__(self): return 'Error #{0.status_code} "{0.message}" ({0.url})'.format(self) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/passivetotal-2.5.1/passivetotal/analyzer/articles.py new/passivetotal-2.5.3/passivetotal/analyzer/articles.py --- old/passivetotal-2.5.1/passivetotal/analyzer/articles.py 2021-06-21 16:37:13.000000000 +0200 +++ new/passivetotal-2.5.3/passivetotal/analyzer/articles.py 2021-07-28 01:47:09.000000000 +0200 @@ -3,7 +3,7 @@ from passivetotal.analyzer._common import ( RecordList, Record, ForPandas ) -from passivetotal.analyzer import get_api +from passivetotal.analyzer import get_api, get_config @@ -71,20 +71,28 @@ By default, instantiating the class will automatically load the entire list of threat intelligence articles. Pass autoload=False to the constructor to disable this functionality. + + Only articles created after the start date specified in the analyzer.set_date_range() + method will be returned unless a different created_after parameter is supplied to the object + constructor. """ - def __init__(self, autoload = True): + def __init__(self, created_after=None, autoload=True): """Initialize a list of articles; will autoload by default. - :param autoload: whether to automatically load articles upon instantiation (defaults to true) """ super().__init__() if autoload: - self.load() + self.load(created_after) - def load(self): - """Query the API for articles and load them into an articles list.""" - response = get_api('Articles').get_articles() + def load(self, created_after=None): + """Query the API for articles and load them into an articles list. + + :param created_after: only return articles created after this date (optional, defaults to date set by `analyzer.set_date_range()` + """ + if created_after is None: + created_after = get_config('start_date') + response = get_api('Articles').get_articles(createdAfter=created_after) self.parse(response) @@ -98,6 +106,7 @@ self._summary = api_response.get('summary') self._type = api_response.get('type') self._publishdate = api_response.get('publishedDate') + self._createdate = api_response.get('createdDate') self._link = api_response.get('link') self._categories = api_response.get('categories') self._tags = api_response.get('tags') @@ -115,13 +124,14 @@ response = get_api('Articles').get_details(self._guid) self._summary = response.get('summary') self._publishdate = response.get('publishedDate') + self._createdate = response.get('createdDate') self._tags = response.get('tags') self._categories = response.get('categories') self._indicators = response.get('indicators') def _get_dict_fields(self): - return ['guid','title','type','summary','str:date_published','age', - 'link','categories','tags','indicators','indicator_count', + return ['guid','title','type','summary','str:date_published','str:date_created', + 'age', 'link','categories','tags','indicators','indicator_count', 'indicator_types','str:ips','str:hostnames'] def _ensure_details(self): @@ -161,6 +171,7 @@ title = self._title, type = self._type, date_published = self._publishdate, + date_created = self._createdate, summary = self._summary, link = self._link, categories = self._categories, @@ -229,10 +240,17 @@ return date @property + def date_created(self): + """Date the article was created in the RiskIQ database.""" + self._ensure_details() + date = datetime.fromisoformat(self._createdate) + return date + + @property def age(self): - """Age of the article in days.""" + """Age of the article in days, measured from create date.""" now = datetime.now(timezone.utc) - interval = now - self.date_published + interval = now - self.date_created return interval.days @property diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/passivetotal-2.5.1/passivetotal/api.py new/passivetotal-2.5.3/passivetotal/api.py --- old/passivetotal-2.5.1/passivetotal/api.py 2021-06-29 20:04:38.000000000 +0200 +++ new/passivetotal-2.5.3/passivetotal/api.py 2021-07-28 01:47:09.000000000 +0200 @@ -1,13 +1,17 @@ """PassiveTotal API Interface.""" -__author__ = 'Brandon Dixon (PassiveTotal)' -__version__ = '1.0.0' import json import logging import requests import sys +from urllib.parse import quote as urlquote from passivetotal.config import Config +from passivetotal._version import VERSION + +__author__ = 'Brandon Dixon (PassiveTotal)' +__version__ = VERSION + class Client(object): @@ -20,7 +24,8 @@ def __init__(self, username, api_key, server=DEFAULT_SERVER, version=DEFAULT_VERSION, http_proxy=None, https_proxy=None, - verify=True, headers=None, debug=False, exception_class=Exception): + verify=True, headers=None, debug=False, exception_class=Exception, + session=None): """Initial loading of the client. :param str username: API username in email address format @@ -58,18 +63,26 @@ if '127.0.0.1' in server: self.verify = False self.exception_class = exception_class + self.set_context('python','passivetotal',VERSION) + self.session = session or requests.Session() @classmethod - def from_config(cls): - """Method to return back a loaded instance.""" + def from_config(cls, **kwargs): + """Method to return back a loaded instance. + + kwargs override configuration file variables if provided and are passed to the object constructor. + """ + arg_keys = ['username','api_key','server','version','http_proxy','https_proxy'] + args = { k: kwargs.pop(k) if k in kwargs else None for k in arg_keys } config = Config() client = cls( - username=config.get('username'), - api_key=config.get('api_key'), - server=config.get('api_server'), - version=config.get('api_version'), - http_proxy=config.get('http_proxy'), - https_proxy=config.get('https_proxy'), + username = args.get('username') or config.get('username'), + api_key = args.get('api_key') or config.get('api_key'), + server = args.get('server') or config.get('api_server'), + version = args.get('version') or config.get('api_version'), + http_proxy = args.get('http_proxy') or config.get('http_proxy'), + https_proxy = args.get('https_proxy') or config.get('https_proxy'), + **kwargs ) return client @@ -78,6 +91,18 @@ self.logger.setLevel('DEBUG') else: self.logger.setLevel('INFO') + + def set_context(self, provider, variant, version, feature=''): + """Set the context for this request. + + :param provider: The company, partner, provider or other top-level application context. + :param variant: The specific app, libary subcomponent, or feature category. + :param version: Version of the app, feature or code setting the context. + :param feature: Optional sub-feature, dashboard or script name. + """ + context = Context(provider, variant, version, feature) + self.context = context + self.headers.update(context.get_header()) def _endpoint(self, endpoint, action, *url_args): """Return the URL for the action. @@ -138,7 +163,7 @@ if self.proxies: kwargs['proxies'] = self.proxies self.logger.debug("Requesting: %s, %s" % (api_url, str(kwargs))) - response = requests.get(api_url, **kwargs) + response = self.session.get(api_url, **kwargs) return self._json(response) def _get_special(self, endpoint, action, trail, data, *url_args, **url_params): @@ -158,7 +183,7 @@ 'auth': (self.username, self.api_key)} if self.proxies: kwargs['proxies'] = self.proxies - response = requests.get(api_url, **kwargs) + response = self.session.get(api_url, **kwargs) return self._json(response) def _send_data(self, method, endpoint, action, @@ -179,5 +204,32 @@ 'auth': (self.username, self.api_key)} if self.proxies: kwargs['proxies'] = self.proxies - response = requests.request(method, api_url, **kwargs) + response = self.session.request(method, api_url, **kwargs) return self._json(response) + + + +class Context: + + """Integration context for a set of API requests.""" + + HEADER_NAME = 'X-RISKIQ-CONTEXT' + + def __init__(self, provider, variant, version, feature = ''): + """Build a new context header. + + :param provider: The company, partner, provider or other top-level application context. + :param variant: The specific app, libary subcomponent, or feature category. + :param version: Version of the app, feature or code setting the context. + :param feature: Optional sub-feature, dashboard or script name. + """ + self._fields = (provider, variant, version, feature) + + def get_header_name(self): + return self.HEADER_NAME + + def get_header_value(self): + return '/'.join(map(lambda f: urlquote(f, safe=''), self._fields)) + + def get_header(self): + return { self.get_header_name() : self.get_header_value() } \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/passivetotal-2.5.1/passivetotal.egg-info/PKG-INFO new/passivetotal-2.5.3/passivetotal.egg-info/PKG-INFO --- old/passivetotal-2.5.1/passivetotal.egg-info/PKG-INFO 2021-06-29 20:04:56.000000000 +0200 +++ new/passivetotal-2.5.3/passivetotal.egg-info/PKG-INFO 2021-07-28 01:48:06.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: passivetotal -Version: 2.5.1 +Version: 2.5.3 Summary: Library for the RiskIQ PassiveTotal and Illuminate API Home-page: https://github.com/passivetotal/python_api Author: RiskIQ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/passivetotal-2.5.1/passivetotal.egg-info/SOURCES.txt new/passivetotal-2.5.3/passivetotal.egg-info/SOURCES.txt --- old/passivetotal-2.5.1/passivetotal.egg-info/SOURCES.txt 2021-06-29 20:04:56.000000000 +0200 +++ new/passivetotal-2.5.3/passivetotal.egg-info/SOURCES.txt 2021-07-28 01:48:06.000000000 +0200 @@ -21,6 +21,7 @@ examples/whois_search.py passivetotal/__init__.py passivetotal/__main__.py +passivetotal/_version.py passivetotal/api.py passivetotal/config.py passivetotal/response.py diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/passivetotal-2.5.1/setup.py new/passivetotal-2.5.3/setup.py --- old/passivetotal-2.5.1/setup.py 2021-06-29 20:04:38.000000000 +0200 +++ new/passivetotal-2.5.3/setup.py 2021-07-15 21:35:42.000000000 +0200 @@ -1,14 +1,21 @@ #!/usr/bin/env python import os +import re from setuptools import setup, find_packages def read(fname): return open(os.path.join(os.path.dirname(__file__), fname)).read() +# pylint: disable=locally-disabled, invalid-name +with open('passivetotal/_version.py', 'r') as fd: + v_match = re.search(r'^VERSION\s*=\s*[\'"]([^\'"]*)[\'"]', fd.read(), re.MULTILINE) + __version__ = v_match.group(1) if v_match else 'no version' +# pylint: enable=locally-disabled, invalid-name + setup( name='passivetotal', - version='2.5.1', + version=__version__, description='Library for the RiskIQ PassiveTotal and Illuminate API', url="https://github.com/passivetotal/python_api", author="RiskIQ",