Code style changes.
parent
97d8d732fc
commit
2a8af45ec2
|
@ -1,4 +1,4 @@
|
||||||
'''
|
"""
|
||||||
Python STIX 2.0 Composite Data Source and Data Source (classes)
|
Python STIX 2.0 Composite Data Source and Data Source (classes)
|
||||||
|
|
||||||
|
|
||||||
|
@ -6,24 +6,28 @@ Python STIX 2.0 Composite Data Source and Data Source (classes)
|
||||||
|
|
||||||
-Test everything
|
-Test everything
|
||||||
|
|
||||||
-add_filter(), remove_filter(), deduplicate() - if these functions remain the exact same for
|
-add_filter(), remove_filter(), deduplicate() - if these functions remain
|
||||||
both CompositeDataSource and DataSource, they just inherit/have module access to
|
the exact same for both CompositeDataSource and DataSource, they just
|
||||||
|
inherit/have module access to
|
||||||
|
|
||||||
'''
|
"""
|
||||||
|
|
||||||
import abc
|
import abc
|
||||||
import copy
|
import copy
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
|
from six import iteritems
|
||||||
|
|
||||||
|
|
||||||
def make_id():
|
def make_id():
|
||||||
str(uuid.uuid4())
|
return str(uuid.uuid4())
|
||||||
|
|
||||||
|
|
||||||
# STIX 2.0 fields used to denote object version
|
# STIX 2.0 fields used to denote object version
|
||||||
STIX_VERSION_FIELDS = ['id', 'modified']
|
STIX_VERSION_FIELDS = ['id', 'modified']
|
||||||
|
|
||||||
# currently, only STIX 2.0 common SDO fields (that are not compex objects) are supported for filtering on
|
# Currently, only STIX 2.0 common SDO fields (that are not compex objects)
|
||||||
|
# are supported for filtering on
|
||||||
STIX_COMMON_FIELDS = [
|
STIX_COMMON_FIELDS = [
|
||||||
'type',
|
'type',
|
||||||
'id',
|
'id',
|
||||||
|
@ -32,11 +36,11 @@ STIX_COMMON_FIELDS = [
|
||||||
'modified',
|
'modified',
|
||||||
'revoked',
|
'revoked',
|
||||||
'labels',
|
'labels',
|
||||||
# 'external_references', #list of external references object type - not supported for filtering
|
# 'external_references', # list of external references object type - not supported for filtering
|
||||||
'object_references',
|
'object_references',
|
||||||
'object_marking_refs',
|
'object_marking_refs',
|
||||||
'granular_marking_refs',
|
'granular_marking_refs',
|
||||||
# 'granular_markings' #list of granular-marking type - not supported for filtering
|
# 'granular_markings' # list of granular-marking type - not supported for filtering
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -51,28 +55,26 @@ FILTER_VALUE_TYPES = [bool, dict, float, int, list, str, tuple]
|
||||||
|
|
||||||
|
|
||||||
class CompositeDataSource(object):
|
class CompositeDataSource(object):
|
||||||
'''Composite Data Source
|
"""Composite Data Source
|
||||||
|
|
||||||
Acts as a controller for all the defined/configured STIX Data Sources
|
Acts as a controller for all the defined/configured STIX Data Sources
|
||||||
e.g. a user can defined n Data Sources - creating Data Source (objects)
|
e.g. a user can defined n Data Sources - creating Data Source (objects)
|
||||||
for each. There is only one instance of this for any python STIX 2.0 application
|
for each. There is only one instance of this for any python STIX 2.0
|
||||||
|
application
|
||||||
|
|
||||||
'''
|
"""
|
||||||
|
|
||||||
def __init__(self, name="CompositeDataSource"):
|
def __init__(self, name="CompositeDataSource"):
|
||||||
'''
|
"""
|
||||||
Creates a new STIX Data Source.
|
Creates a new STIX Data Source.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
'data_sources' (dict): a dict of DataSource objects; to be controlled and used by
|
'data_sources' (dict): a dict of DataSource objects; to be
|
||||||
the Data Source Controller object
|
controlled and used by the Data Source Controller object
|
||||||
|
|
||||||
filters :
|
filters :
|
||||||
name :
|
name :
|
||||||
|
"""
|
||||||
Returns:
|
|
||||||
|
|
||||||
'''
|
|
||||||
self.id_ = make_id()
|
self.id_ = make_id()
|
||||||
self.name = name
|
self.name = name
|
||||||
self.data_sources = {}
|
self.data_sources = {}
|
||||||
|
@ -80,7 +82,7 @@ class CompositeDataSource(object):
|
||||||
self.filter_allowed = {}
|
self.filter_allowed = {}
|
||||||
|
|
||||||
def get(self, id_):
|
def get(self, id_):
|
||||||
'''retrieve STIX object by 'id'
|
"""Retrieve STIX object by 'id'
|
||||||
|
|
||||||
federated retrieve method-iterates through all STIX data sources
|
federated retrieve method-iterates through all STIX data sources
|
||||||
defined in the "data_sources" parameter. Each data source has a
|
defined in the "data_sources" parameter. Each data source has a
|
||||||
|
@ -97,12 +99,12 @@ class CompositeDataSource(object):
|
||||||
Returns:
|
Returns:
|
||||||
stix_obj (dict): the STIX object to be returned
|
stix_obj (dict): the STIX object to be returned
|
||||||
|
|
||||||
'''
|
"""
|
||||||
|
|
||||||
all_data = []
|
all_data = []
|
||||||
|
|
||||||
# for every configured Data Source, call its retrieve handler
|
# for every configured Data Source, call its retrieve handler
|
||||||
for ds_id, ds in self.data_sources.iteritems():
|
for ds_id, ds in iteritems(self.data_sources):
|
||||||
data = ds.get(id_=id_, _composite_filters=self.filters.values())
|
data = ds.get(id_=id_, _composite_filters=self.filters.values())
|
||||||
all_data += data
|
all_data += data
|
||||||
|
|
||||||
|
@ -116,7 +118,7 @@ class CompositeDataSource(object):
|
||||||
return stix_obj
|
return stix_obj
|
||||||
|
|
||||||
def all_versions(self, id_):
|
def all_versions(self, id_):
|
||||||
'''retrieve STIX objects by 'id'
|
"""Retrieve STIX objects by 'id'
|
||||||
|
|
||||||
Federated all_versions retrieve method - iterates through all STIX data
|
Federated all_versions retrieve method - iterates through all STIX data
|
||||||
sources defined in "data_sources"
|
sources defined in "data_sources"
|
||||||
|
@ -129,22 +131,23 @@ class CompositeDataSource(object):
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
all_data (list): list of STIX objects that have the specified id
|
all_data (list): list of STIX objects that have the specified id
|
||||||
'''
|
"""
|
||||||
all_data = []
|
all_data = []
|
||||||
|
|
||||||
# retrieve STIX objects from all configured data sources
|
# retrieve STIX objects from all configured data sources
|
||||||
for ds_id, ds in self.data_sources.iteritems():
|
for ds_id, ds in iteritems(self.data_sources):
|
||||||
data = ds.all_versions(id_=id_, _composite_filters=self.filters.values())
|
data = ds.all_versions(id_=id_, _composite_filters=self.filters.values())
|
||||||
all_data += data
|
all_data += data
|
||||||
|
|
||||||
# remove exact duplicates (where duplicates are STIX 2.0 objects with the same 'id' and 'modified' values)
|
# remove exact duplicates (where duplicates are STIX 2.0 objects
|
||||||
|
# with the same 'id' and 'modified' values)
|
||||||
if len(all_data) > 0:
|
if len(all_data) > 0:
|
||||||
all_data = self.deduplicate(all_data)
|
all_data = self.deduplicate(all_data)
|
||||||
|
|
||||||
return all_data
|
return all_data
|
||||||
|
|
||||||
def query(self, query=None):
|
def query(self, query=None):
|
||||||
'''composite data source query
|
"""composite data source query
|
||||||
|
|
||||||
Federate the query to all Data Sources attached
|
Federate the query to all Data Sources attached
|
||||||
to the Composite Data Source
|
to the Composite Data Source
|
||||||
|
@ -155,32 +158,35 @@ class CompositeDataSource(object):
|
||||||
Returns:
|
Returns:
|
||||||
all_data (list): list of STIX objects to be returned
|
all_data (list): list of STIX objects to be returned
|
||||||
|
|
||||||
'''
|
"""
|
||||||
if not query:
|
if not query:
|
||||||
query = []
|
query = []
|
||||||
|
|
||||||
all_data = []
|
all_data = []
|
||||||
|
|
||||||
# federate query to all attached data sources, pass composite filters to them
|
# federate query to all attached data sources,
|
||||||
for ds_id, ds in self.data_sources.iteritems():
|
# pass composite filters to them
|
||||||
|
for ds_id, ds in iteritems(self.data_sources):
|
||||||
data = ds.query(query=query, _composite_filters=self.filters.values())
|
data = ds.query(query=query, _composite_filters=self.filters.values())
|
||||||
all_data += data
|
all_data += data
|
||||||
|
|
||||||
# remove exact duplicates (where duplicates are STIX 2.0 objects with the same 'id' and 'modified' values)
|
# remove exact duplicates (where duplicates are STIX 2.0
|
||||||
|
# objects with the same 'id' and 'modified' values)
|
||||||
if len(all_data) > 0:
|
if len(all_data) > 0:
|
||||||
all_data = self.deduplicate(all_data)
|
all_data = self.deduplicate(all_data)
|
||||||
|
|
||||||
return all_data
|
return all_data
|
||||||
|
|
||||||
def add_data_source(self, data_sources):
|
def add_data_source(self, data_sources):
|
||||||
'''add/attach Data Source to the Composite Data Source instance
|
"""add/attach Data Source to the Composite Data Source instance
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
data_sources (list): a list of Data Source objects to attach to the Composite Data Source
|
data_sources (list): a list of Data Source objects to attach
|
||||||
|
to the Composite Data Source
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
'''
|
"""
|
||||||
|
|
||||||
for ds in data_sources:
|
for ds in data_sources:
|
||||||
if issubclass(ds, DataSource):
|
if issubclass(ds, DataSource):
|
||||||
|
@ -188,37 +194,40 @@ class CompositeDataSource(object):
|
||||||
# data source already attached to Composite Data Source
|
# data source already attached to Composite Data Source
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# add data source to Composite Data Source (its id will be its key identifier)
|
# add data source to Composite Data Source
|
||||||
|
# (its id will be its key identifier)
|
||||||
self.data_sources[ds['id']] = ds
|
self.data_sources[ds['id']] = ds
|
||||||
else:
|
else:
|
||||||
# the Data Source object is not a proper subclass of DataSource Abstract Class
|
# the Data Source object is not a proper subclass
|
||||||
|
# of DataSource Abstract Class
|
||||||
# TODO: maybe log error?
|
# TODO: maybe log error?
|
||||||
continue
|
continue
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def remove_data_source(self, data_source_ids):
|
def remove_data_source(self, data_source_ids):
|
||||||
'''remove/detach Data Source from the Composite Data Source instance
|
"""remove/detach Data Source from the Composite Data Source instance
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
data_source_ids (list): a list of Data Source id's( which are strings )
|
data_source_ids (list): a list of Data Source
|
||||||
|
id's(which are strings)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
'''
|
|
||||||
|
|
||||||
for id_ in data_source_ids:
|
for id_ in data_source_ids:
|
||||||
try:
|
try:
|
||||||
if self.data_sources[id_]:
|
if self.data_sources[id_]:
|
||||||
del self.data_sources[id_]
|
del self.data_sources[id_]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
# Data Source 'id' was not found in CompositeDataSource's list of data sources
|
# Data Source 'id' was not found in CompositeDataSource's
|
||||||
|
# list of data sources
|
||||||
pass
|
pass
|
||||||
return
|
return
|
||||||
|
|
||||||
def get_data_sources(self):
|
def get_data_sources(self):
|
||||||
'''return all attached Data Sources
|
"""return all attached Data Sources
|
||||||
|
|
||||||
TODO: Make this a property?
|
TODO: Make this a property?
|
||||||
|
|
||||||
|
@ -226,11 +235,11 @@ class CompositeDataSource(object):
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
'''
|
"""
|
||||||
return copy.deepcopy(self.data_sources.values())
|
return copy.deepcopy(self.data_sources.values())
|
||||||
|
|
||||||
def add_filter(self, filters):
|
def add_filter(self, filters):
|
||||||
'''add/attach a filter to the Composite Data Source instance
|
"""add/attach a filter to the Composite Data Source instance
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
filters (list): list of filters (dict) to add to the Data Source
|
filters (list): list of filters (dict) to add to the Data Source
|
||||||
|
@ -238,7 +247,7 @@ class CompositeDataSource(object):
|
||||||
Returns:
|
Returns:
|
||||||
status (list): list of status/error messages
|
status (list): list of status/error messages
|
||||||
|
|
||||||
'''
|
"""
|
||||||
|
|
||||||
status = []
|
status = []
|
||||||
errors = []
|
errors = []
|
||||||
|
@ -268,12 +277,11 @@ class CompositeDataSource(object):
|
||||||
allowed = False
|
allowed = False
|
||||||
errors.append("Filter 'value' type is not supported. The type(value) must be python immutable type or dictionary")
|
errors.append("Filter 'value' type is not supported. The type(value) must be python immutable type or dictionary")
|
||||||
|
|
||||||
'''
|
# Filter is added regardless of whether it fits requirements
|
||||||
Filter is added regardless of whether it fits requirements
|
# to be a common filter. This is done because some filters
|
||||||
to be a common filter. This is done because some filters
|
# may be added and used by third party Data Sources, where the
|
||||||
may be added and used by third party Data Sources, where
|
# filtering may be conducted within those plugins, just not here
|
||||||
the filtering may be conducted within those plugins, just not here
|
|
||||||
'''
|
|
||||||
id_ = make_id()
|
id_ = make_id()
|
||||||
filter_['id'] = id_
|
filter_['id'] = id_
|
||||||
self.filters['id_'] = filter_
|
self.filters['id_'] = filter_
|
||||||
|
@ -302,7 +310,7 @@ class CompositeDataSource(object):
|
||||||
return ids, status
|
return ids, status
|
||||||
|
|
||||||
def remove_filter(self, filter_ids):
|
def remove_filter(self, filter_ids):
|
||||||
'''remove/detach a filter from the Data Source instance
|
"""Remove/detach a filter from the Data Source instance
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
filter_ids (list): list of filter id's (which are strings)
|
filter_ids (list): list of filter id's (which are strings)
|
||||||
|
@ -310,7 +318,7 @@ class CompositeDataSource(object):
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
'''
|
"""
|
||||||
|
|
||||||
for filter_id in filter_ids:
|
for filter_id in filter_ids:
|
||||||
try:
|
try:
|
||||||
|
@ -318,35 +326,36 @@ class CompositeDataSource(object):
|
||||||
del self.filters[filter_id]
|
del self.filters[filter_id]
|
||||||
del self.filter_allowed[filter_id]
|
del self.filter_allowed[filter_id]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
# filter id not found in list of filters attached to the Composite Data Source
|
# filter id not found in list of filters
|
||||||
|
# attached to the Composite Data Source
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def get_filters(self):
|
def get_filters(self):
|
||||||
'''return filters attached to Composite Data Source
|
"""return filters attached to Composite Data Source
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
(list): the list of filters currently attached to the Data Source
|
(list): the list of filters currently attached to the Data Source
|
||||||
|
|
||||||
'''
|
"""
|
||||||
return copy.deepcopy(list(self.filters.values()))
|
return copy.deepcopy(list(self.filters.values()))
|
||||||
|
|
||||||
def deduplicate(self, stix_obj_list):
|
def deduplicate(self, stix_obj_list):
|
||||||
'''deduplicate a list fo STIX objects to a unique set
|
"""deduplicate a list fo STIX objects to a unique set
|
||||||
|
|
||||||
Reduces a set of STIX objects to unique set by looking
|
Reduces a set of STIX objects to unique set by looking
|
||||||
at 'id' and 'modified' fields - as a unique object version is determined
|
at 'id' and 'modified' fields - as a unique object version
|
||||||
by the combination of those fields
|
is determined by the combination of those fields
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
stix_obj_list (list): list of STIX objects (dicts)
|
stix_obj_list (list): list of STIX objects (dicts)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
(list): unique set of the passed list of STIX objects
|
(list): unique set of the passed list of STIX objects
|
||||||
'''
|
"""
|
||||||
|
|
||||||
unique = []
|
unique = []
|
||||||
dont_have = False
|
dont_have = False
|
||||||
|
@ -363,7 +372,7 @@ class CompositeDataSource(object):
|
||||||
|
|
||||||
|
|
||||||
class DataSource(object):
|
class DataSource(object):
|
||||||
'''
|
"""
|
||||||
Abstract Data Source class for STIX 2.0
|
Abstract Data Source class for STIX 2.0
|
||||||
|
|
||||||
An implementer will create a concrete subclass from
|
An implementer will create a concrete subclass from
|
||||||
|
@ -373,7 +382,7 @@ class DataSource(object):
|
||||||
supply them to a Composite Data Source which calls
|
supply them to a Composite Data Source which calls
|
||||||
the subclass methods when conducting STIX 2.0
|
the subclass methods when conducting STIX 2.0
|
||||||
data retrievals.
|
data retrievals.
|
||||||
'''
|
"""
|
||||||
|
|
||||||
__metaclass__ = abc.ABCMeta
|
__metaclass__ = abc.ABCMeta
|
||||||
|
|
||||||
|
@ -385,87 +394,93 @@ class DataSource(object):
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def get(self, id_, _composite_filters=None):
|
def get(self, id_, _composite_filters=None):
|
||||||
'''
|
"""
|
||||||
Fill:
|
Fill:
|
||||||
-implement the specific data source API calls, processing, functionality
|
-implement the specific data source API calls, processing,
|
||||||
requried for retrieving data from the data source
|
functionality required for retrieving data from the data source
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
||||||
id (str): the id of the STIX 2.0 object to retrieve. Should return a single object,
|
id_ (str): the id of the STIX 2.0 object to retrieve. Should
|
||||||
the most recent version of the object specified by the "id".
|
return a single object, the most recent version of the object
|
||||||
|
specified by the "id".
|
||||||
|
|
||||||
_composite_filters (list): list of filters passed along from the Composite Data Filter
|
_composite_filters (list): list of filters passed along from
|
||||||
|
the Composite Data Filter.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
stix_obj (dictionary): the STIX object to be returned
|
stix_obj (dictionary): the STIX object to be returned
|
||||||
|
|
||||||
'''
|
"""
|
||||||
stix_obj = None
|
stix_obj = None
|
||||||
|
|
||||||
return stix_obj
|
return stix_obj
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def all_versions(self, id_, _composite_filters=None):
|
def all_versions(self, id_, _composite_filters=None):
|
||||||
'''
|
"""
|
||||||
Fill:
|
Fill:
|
||||||
-Similar to get() except returns list of all object versions of the specified "id".
|
-Similar to get() except returns list of all object versions of
|
||||||
|
the specified "id".
|
||||||
-implement the specific data source API calls, processing, functionality
|
|
||||||
requried for retrieving data from the data source
|
|
||||||
|
|
||||||
|
-implement the specific data source API calls, processing,
|
||||||
|
functionality required for retrieving data from the data source
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
id (str): The id of the STIX 2.0 object to retrieve. Should return a list of objects,
|
id_ (str): The id of the STIX 2.0 object to retrieve. Should
|
||||||
all the versions of the object specified by the "id".
|
return a list of objects, all the versions of the object
|
||||||
|
specified by the "id".
|
||||||
|
|
||||||
_composite_filters (list): list of filters passed from the Composite Data Source
|
_composite_filters (list): list of filters passed from the
|
||||||
|
Composite Data Source
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
stix_objs (list): a list of STIX objects(where each object is a STIX object)
|
stix_objs (list): a list of STIX objects (where each object is a
|
||||||
|
STIX object)
|
||||||
'''
|
"""
|
||||||
stix_objs = []
|
stix_objs = []
|
||||||
|
|
||||||
return stix_objs
|
return stix_objs
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def query(self, query, _composite_filters=None):
|
def query(self, query, _composite_filters=None):
|
||||||
'''
|
"""
|
||||||
Fill:
|
Fill:
|
||||||
-implement the specific data source API calls, processing, functionality
|
-implement the specific data source API calls, processing,
|
||||||
requried for retrieving query from the data source
|
functionality required for retrieving query from the data source
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
query (list): a list of filters (which collectively are the query) to conduct search on
|
query (list): a list of filters (which collectively are the query)
|
||||||
|
to conduct search on
|
||||||
|
|
||||||
_composite_filters (list): a list of filters passed from the Composite Data Source
|
_composite_filters (list): a list of filters passed from the
|
||||||
|
Composite Data Source
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
|
|
||||||
'''
|
"""
|
||||||
stix_objs = []
|
stix_objs = []
|
||||||
|
|
||||||
return stix_objs
|
return stix_objs
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def close(self):
|
def close(self):
|
||||||
'''
|
"""
|
||||||
Fill:
|
Fill:
|
||||||
Close, release, shutdown any objects, contexts, variables
|
Close, release, shutdown any objects, contexts, variables
|
||||||
Args:
|
Args:
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
(list): list of status/error messages
|
(list): list of status/error messages
|
||||||
'''
|
"""
|
||||||
|
|
||||||
status = []
|
status = []
|
||||||
|
|
||||||
return status
|
return status
|
||||||
|
|
||||||
def add_filter(self, filters):
|
def add_filter(self, filters):
|
||||||
'''add/attach a filter to the Data Source instance
|
"""add/attach a filter to the Data Source instance
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
filters (list): list of filters (dict) to add to the Data Source
|
filters (list): list of filters (dict) to add to the Data Source
|
||||||
|
@ -473,7 +488,7 @@ class DataSource(object):
|
||||||
Returns:
|
Returns:
|
||||||
status (list): list of status/error messages
|
status (list): list of status/error messages
|
||||||
|
|
||||||
'''
|
"""
|
||||||
|
|
||||||
status = []
|
status = []
|
||||||
errors = []
|
errors = []
|
||||||
|
@ -503,12 +518,11 @@ class DataSource(object):
|
||||||
allowed = False
|
allowed = False
|
||||||
errors.append("Filter 'value' type is not supported. The type(value) must be python immutable type or dictionary")
|
errors.append("Filter 'value' type is not supported. The type(value) must be python immutable type or dictionary")
|
||||||
|
|
||||||
'''
|
# Filter is added regardless of whether it fits requirements
|
||||||
Filter is added regardless of whether it fits requirements
|
# to be a common filter. This is done because some filters
|
||||||
to be a common filter. This is done because some filters
|
# may be added and used by third party Data Sources, where the
|
||||||
may be added and used by third party Data Sources, where
|
# filtering may be conducted within those plugins, just not here
|
||||||
the filtering may be conducted within those plugins, just not here
|
|
||||||
'''
|
|
||||||
id_ = make_id()
|
id_ = make_id()
|
||||||
filter_['id'] = id_
|
filter_['id'] = id_
|
||||||
self.filters[id_] = filter_
|
self.filters[id_] = filter_
|
||||||
|
@ -538,15 +552,16 @@ class DataSource(object):
|
||||||
return ids, status
|
return ids, status
|
||||||
|
|
||||||
def remove_filter(self, filter_ids):
|
def remove_filter(self, filter_ids):
|
||||||
'''remove/detach a filter from the Data Source instance
|
"""remove/detach a filter from the Data Source instance
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
filter_ids (list): list of filter ids to dettach/remove from Data Source
|
filter_ids (list): list of filter ids to dettach/remove
|
||||||
|
from Data Source
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
|
|
||||||
'''
|
"""
|
||||||
for filter_id in filter_ids:
|
for filter_id in filter_ids:
|
||||||
try:
|
try:
|
||||||
if filter_id in self.filters:
|
if filter_id in self.filters:
|
||||||
|
@ -559,18 +574,19 @@ class DataSource(object):
|
||||||
return
|
return
|
||||||
|
|
||||||
def get_filters(self):
|
def get_filters(self):
|
||||||
'''return copy of all filters currently attached to Data Source
|
"""return copy of all filters currently attached to Data Source
|
||||||
|
|
||||||
TODO: make this a property?
|
TODO: make this a property?
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
(list): a copy of all the filters(dict) which are attached to Data Source
|
(list): a copy of all the filters(dict) which are attached
|
||||||
|
to Data Source
|
||||||
|
|
||||||
'''
|
"""
|
||||||
return copy.deepcopy(list(self.filters.values()))
|
return copy.deepcopy(list(self.filters.values()))
|
||||||
|
|
||||||
def apply_common_filters(self, stix_objs, query):
|
def apply_common_filters(self, stix_objs, query):
|
||||||
'''evaluates filters against a set of STIX 2.0 objects
|
"""evaluates filters against a set of STIX 2.0 objects
|
||||||
|
|
||||||
Supports only STIX 2.0 common property fields
|
Supports only STIX 2.0 common property fields
|
||||||
|
|
||||||
|
@ -579,9 +595,10 @@ class DataSource(object):
|
||||||
query (list): list of filters (combined form complete query)
|
query (list): list of filters (combined form complete query)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
(list): list of STIX objects that successfully evaluate against the query
|
(list): list of STIX objects that successfully evaluate against
|
||||||
|
the query
|
||||||
|
|
||||||
'''
|
"""
|
||||||
|
|
||||||
filtered_stix_objs = []
|
filtered_stix_objs = []
|
||||||
|
|
||||||
|
@ -590,12 +607,14 @@ class DataSource(object):
|
||||||
clean = True
|
clean = True
|
||||||
for filter_ in query:
|
for filter_ in query:
|
||||||
|
|
||||||
# skip filter as filter was identified (when added) as not a common filter
|
# skip filter as filter was identified (when added) as
|
||||||
|
# not a common filter
|
||||||
if 'id' in filter_ and self.filter_allowed[filter_['id']] is False:
|
if 'id' in filter_ and self.filter_allowed[filter_['id']] is False:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# check filter "field" is in STIX object - if cant be applied due to STIX object,
|
# check filter "field" is in STIX object - if cant be applied
|
||||||
# STIX object is discarded (i.e. did not make it through the filter)
|
# due to STIX object, STIX object is discarded (i.e. did not
|
||||||
|
# make it through the filter)
|
||||||
if filter_['field'] not in stix_obj.keys():
|
if filter_['field'] not in stix_obj.keys():
|
||||||
break
|
break
|
||||||
|
|
||||||
|
@ -616,34 +635,34 @@ class DataSource(object):
|
||||||
else:
|
else:
|
||||||
# filter operation not supported
|
# filter operation not supported
|
||||||
continue
|
continue
|
||||||
'''
|
|
||||||
#TODO: I think the rest of the operations only
|
|
||||||
#apply to timestamps, in which case I dont think
|
|
||||||
#simple operator usage (like below) works
|
|
||||||
|
|
||||||
elif filter_['op'] == ">":
|
# TODO: I think the rest of the operations only
|
||||||
if not stix_obj[filter_['field']] > filter_['value']:
|
# apply to timestamps, in which case I don't think
|
||||||
clean = False
|
# simple operator usage (like below) works
|
||||||
break
|
|
||||||
|
|
||||||
elif filter_['op'] == "<":
|
# elif filter_['op'] == ">":
|
||||||
if not stix_obj[filter_['field']] < filter_['value']:
|
# if not stix_obj[filter_['field']] > filter_['value']:
|
||||||
clean = False
|
# clean = False
|
||||||
break
|
# break
|
||||||
|
#
|
||||||
elif filter_['op'] == ">=":
|
# elif filter_['op'] == "<":
|
||||||
if not stix_obj[filter_['field']] >= filter_['value']:
|
# if not stix_obj[filter_['field']] < filter_['value']:
|
||||||
clean = False
|
# clean = False
|
||||||
break
|
# break
|
||||||
|
#
|
||||||
elif filter_['op'] == "<=":
|
# elif filter_['op'] == ">=":
|
||||||
if not stix_obj[filter_['field']] <= filter_['value']:
|
# if not stix_obj[filter_['field']] >= filter_['value']:
|
||||||
clean = False
|
# clean = False
|
||||||
break
|
# break
|
||||||
'''
|
#
|
||||||
|
# elif filter_['op'] == "<=":
|
||||||
|
# if not stix_obj[filter_['field']] <= filter_['value']:
|
||||||
|
# clean = False
|
||||||
|
# break
|
||||||
|
|
||||||
except TypeError:
|
except TypeError:
|
||||||
# type mismatch of comparison operands - ignore filter, no error raised for now
|
# type mismatch of comparison operands - ignore filter,
|
||||||
|
# no error raised for now
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# if object unmarked after all filter, add it
|
# if object unmarked after all filter, add it
|
||||||
|
@ -655,7 +674,7 @@ class DataSource(object):
|
||||||
return filtered_stix_objs
|
return filtered_stix_objs
|
||||||
|
|
||||||
def deduplicate(self, stix_obj_list):
|
def deduplicate(self, stix_obj_list):
|
||||||
'''deduplicate a list of STIX objects into a unique set
|
"""deduplicate a list of STIX objects into a unique set
|
||||||
|
|
||||||
reduces a set of STIX objects to unique set by looking
|
reduces a set of STIX objects to unique set by looking
|
||||||
at 'id' and 'modified' fields - as a unique object version
|
at 'id' and 'modified' fields - as a unique object version
|
||||||
|
@ -668,7 +687,7 @@ class DataSource(object):
|
||||||
(list): a unique set of the passed STIX object list
|
(list): a unique set of the passed STIX object list
|
||||||
|
|
||||||
|
|
||||||
'''
|
"""
|
||||||
unique = []
|
unique = []
|
||||||
have = False
|
have = False
|
||||||
for i in stix_obj_list:
|
for i in stix_obj_list:
|
||||||
|
|
|
@ -3,27 +3,23 @@ from requests.auth import HTTPBasicAuth
|
||||||
|
|
||||||
from stix2.sources import DataSource
|
from stix2.sources import DataSource
|
||||||
|
|
||||||
'''
|
# TODO: -Should we make properties for the TAXIIDataSource address and other
|
||||||
TODO:
|
# possible variables that are found in "self.taxii_info"
|
||||||
|
|
||||||
-Should we make properties for the TAXIIDataSource address and other possible variables
|
|
||||||
that are found in "self.taxii_info"
|
|
||||||
'''
|
|
||||||
|
|
||||||
TAXII_FILTERS = ['added_after', 'match[id]', 'match[type]', 'match[version]']
|
TAXII_FILTERS = ['added_after', 'match[id]', 'match[type]', 'match[version]']
|
||||||
|
|
||||||
|
|
||||||
class TAXIIDataSource(DataSource):
|
class TAXIIDataSource(DataSource):
|
||||||
'''STIX 2.0 Data Source - TAXII 2.0 module'''
|
"""STIX 2.0 Data Source - TAXII 2.0 module"""
|
||||||
|
|
||||||
def __init__(self, api_root=None, auth=None, name="TAXII", ):
|
|
||||||
|
|
||||||
|
def __init__(self, api_root=None, auth=None, name="TAXII"):
|
||||||
super(TAXIIDataSource, self).__init__(name=name)
|
super(TAXIIDataSource, self).__init__(name=name)
|
||||||
|
|
||||||
self.taxii_info = {
|
self.taxii_info = {
|
||||||
"api_root": {
|
"api_root": {
|
||||||
"url": api_root
|
"url": api_root
|
||||||
},
|
},
|
||||||
"auth": auth
|
"auth": auth
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,7 +30,8 @@ class TAXIIDataSource(DataSource):
|
||||||
|
|
||||||
resp = requests.get(coll_url,
|
resp = requests.get(coll_url,
|
||||||
headers=headers,
|
headers=headers,
|
||||||
auth=HTTPBasicAuth(self.taxii_info['auth']['user'], self.taxii_info['auth']['pass']))
|
auth=HTTPBasicAuth(self.taxii_info['auth']['user'],
|
||||||
|
self.taxii_info['auth']['pass']))
|
||||||
# TESTING
|
# TESTING
|
||||||
# print("\n-------__init__() ----\n")
|
# print("\n-------__init__() ----\n")
|
||||||
# print(resp.text)
|
# print(resp.text)
|
||||||
|
@ -53,8 +50,10 @@ class TAXIIDataSource(DataSource):
|
||||||
if e == "collections":
|
if e == "collections":
|
||||||
raise
|
raise
|
||||||
# raise type(e), type(e)(e.message +
|
# raise type(e), type(e)(e.message +
|
||||||
# "To connect to the TAXII collections, the API root resource must contain a collection endpoint URL.
|
# "To connect to the TAXII collections, the API root
|
||||||
# This was not found in the API root resource received from the API root" ), sys.exc_info()[2]
|
# resource must contain a collection endpoint URL.
|
||||||
|
# This was not found in the API root resource received
|
||||||
|
# from the API root" ), sys.exc_info()[2]
|
||||||
|
|
||||||
except requests.ConnectionError as e:
|
except requests.ConnectionError as e:
|
||||||
raise
|
raise
|
||||||
|
@ -62,20 +61,21 @@ class TAXIIDataSource(DataSource):
|
||||||
# "Attempting to connect to %s" % coll_url)
|
# "Attempting to connect to %s" % coll_url)
|
||||||
|
|
||||||
def get(self, id_, _composite_filters=None):
|
def get(self, id_, _composite_filters=None):
|
||||||
'''get STIX 2 object from TAXII source by specified 'id'
|
"""Get STIX 2.0 object from TAXII source by specified 'id'
|
||||||
|
|
||||||
NOTE:
|
Notes:
|
||||||
-just pass _composite_filters to the query() as they are applied there
|
Just pass _composite_filters to the query() as they are applied
|
||||||
-deduplication of results is also done within query()
|
there. de-duplication of results is also done within query()
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
id_ (str): id of STIX object to retrieve
|
id_ (str): id of STIX object to retrieve
|
||||||
|
|
||||||
_composite_filters (list): filters passed from a Composite Data Source (if this data source is attached to one)
|
_composite_filters (list): filters passed from a Composite Data
|
||||||
|
Source (if this data source is attached to one)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
'''
|
"""
|
||||||
|
|
||||||
# make query in TAXII query format since 'id' is TAXii field
|
# make query in TAXII query format since 'id' is TAXii field
|
||||||
query = [
|
query = [
|
||||||
|
@ -94,20 +94,21 @@ class TAXIIDataSource(DataSource):
|
||||||
return stix_obj
|
return stix_obj
|
||||||
|
|
||||||
def all_versions(self, id_, _composite_filters=None):
|
def all_versions(self, id_, _composite_filters=None):
|
||||||
'''get all versions of STIX 2 object from TAXII source by specified 'id'
|
"""Get all versions of STIX 2.0 object from TAXII source by
|
||||||
|
specified 'id'
|
||||||
|
|
||||||
NOTE:
|
Notes:
|
||||||
-just passes _composite_filters to the query() as they are applied there
|
Just passes _composite_filters to the query() as they are applied
|
||||||
-deduplication of results is also done within query()
|
there. de-duplication of results is also done within query()
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
id_ (str): id of STIX objects to retrieve
|
id_ (str): id of STIX objects to retrieve
|
||||||
|
_composite_filters (list): filters passed from a Composite Data
|
||||||
_composite_filters (list): filters passed from a Composite Data Source (if this data source is attached to one)
|
Source (if this data source is attached to one)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
The query results with filters applied.
|
||||||
'''
|
"""
|
||||||
|
|
||||||
# make query in TAXII query format since 'id' is TAXII field
|
# make query in TAXII query format since 'id' is TAXII field
|
||||||
query = [
|
query = [
|
||||||
|
@ -123,15 +124,16 @@ class TAXIIDataSource(DataSource):
|
||||||
return all_data
|
return all_data
|
||||||
|
|
||||||
def query(self, query=None, _composite_filters=None):
|
def query(self, query=None, _composite_filters=None):
|
||||||
'''query the TAXII data source for STIX objects matching the query
|
"""Query the TAXII data source for STIX objects matching the query
|
||||||
|
|
||||||
The final full query could contain filters from:
|
The final full query could contain filters from:
|
||||||
-the current API call
|
-the current API call
|
||||||
-Composite Data source filters (that are passed in via '_composite_filters')
|
-Composite Data source filters (that are passed in via
|
||||||
|
'_composite_filters')
|
||||||
-TAXII data source filters that are attached
|
-TAXII data source filters that are attached
|
||||||
|
|
||||||
TAXII filters ['added_after', 'match[<>]'] are extracted and sent to TAXII
|
TAXII filters ['added_after', 'match[<>]'] are extracted and sent
|
||||||
if they are present
|
to TAXII if they are present
|
||||||
|
|
||||||
TODO: Authentication for TAXII
|
TODO: Authentication for TAXII
|
||||||
|
|
||||||
|
@ -139,12 +141,13 @@ class TAXIIDataSource(DataSource):
|
||||||
|
|
||||||
query(list): list of filters (dicts) to search on
|
query(list): list of filters (dicts) to search on
|
||||||
|
|
||||||
_composite_filters (list): filters passed from a Composite Data Source (if this data source is attached to one)
|
_composite_filters (list): filters passed from a
|
||||||
|
Composite Data Source (if this data source is attached to one)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
|
|
||||||
'''
|
"""
|
||||||
|
|
||||||
all_data = []
|
all_data = []
|
||||||
|
|
||||||
|
@ -157,24 +160,27 @@ class TAXIIDataSource(DataSource):
|
||||||
if _composite_filters:
|
if _composite_filters:
|
||||||
query += _composite_filters
|
query += _composite_filters
|
||||||
|
|
||||||
# seperate taxii query terms (can be done remotely)
|
# separate taxii query terms (can be done remotely)
|
||||||
taxii_filters = self._parse_taxii_filters(query)
|
taxii_filters = self._parse_taxii_filters(query)
|
||||||
|
|
||||||
# for each collection endpoint - send query request
|
# for each collection endpoint - send query request
|
||||||
for collection in self.taxii_info['api_root']['collections']:
|
for collection in self.taxii_info['api_root']['collections']:
|
||||||
|
|
||||||
coll_obj_url = self.taxii_info['api_root']['url'] + "/collections/" + str(collection['id']) + "/objects/"
|
coll_obj_url = "/".join([self.taxii_info['api_root']['url'],
|
||||||
|
"collections", str(collection['id']),
|
||||||
|
"objects"])
|
||||||
headers = {}
|
headers = {}
|
||||||
try:
|
try:
|
||||||
resp = requests.get(coll_obj_url,
|
resp = requests.get(coll_obj_url,
|
||||||
params=taxii_filters,
|
params=taxii_filters,
|
||||||
headers=headers,
|
headers=headers,
|
||||||
auth=HTTPBasicAuth(self.taxii_info['auth']['user'], self.taxii_info['auth']['pass']))
|
auth=HTTPBasicAuth(self.taxii_info['auth']['user'],
|
||||||
|
self.taxii_info['auth']['pass']))
|
||||||
# TESTING
|
# TESTING
|
||||||
# print("\n-------query() ----\n")
|
# print("\n-------query() ----\n")
|
||||||
# print("Request that was sent: \n")
|
# print("Request that was sent: \n")
|
||||||
# print(resp.url)
|
# print(resp.url)
|
||||||
# print("Reponse: \n")
|
# print("Response: \n")
|
||||||
# print(json.dumps(resp.json(),indent=4))
|
# print(json.dumps(resp.json(),indent=4))
|
||||||
# print("\n")
|
# print("\n")
|
||||||
# print(resp.status_code)
|
# print(resp.status_code)
|
||||||
|
@ -194,9 +200,8 @@ class TAXIIDataSource(DataSource):
|
||||||
# raise type(e), type(e)(e.message +
|
# raise type(e), type(e)(e.message +
|
||||||
# "Attempting to connect to %s" % coll_url)
|
# "Attempting to connect to %s" % coll_url)
|
||||||
|
|
||||||
'''
|
# TODO: Is there a way to collect exceptions while carrying
|
||||||
TODO: Is there a way to collect exceptions while carrying on then raise all of them at the end?
|
# on then raise all of them at the end?
|
||||||
'''
|
|
||||||
|
|
||||||
# deduplicate data (before filtering as reduces wasted filtering)
|
# deduplicate data (before filtering as reduces wasted filtering)
|
||||||
all_data = self.deduplicate(all_data)
|
all_data = self.deduplicate(all_data)
|
||||||
|
@ -207,24 +212,25 @@ class TAXIIDataSource(DataSource):
|
||||||
return all_data
|
return all_data
|
||||||
|
|
||||||
def _parse_taxii_filters(self, query):
|
def _parse_taxii_filters(self, query):
|
||||||
'''parse out TAXII filters that the TAXII server can filter on
|
"""Parse out TAXII filters that the TAXII server can filter on
|
||||||
|
|
||||||
TAXII filters should be analgous to how they are supplied
|
TAXII filters should be analgous to how they are supplied
|
||||||
in the url to the TAXII endpoint. For instance
|
in the url to the TAXII endpoint. For instance
|
||||||
"?match[type]=indicator,sighting" should be in a query dict as follows
|
"?match[type]=indicator,sighting" should be in a query dict as follows
|
||||||
{
|
{
|
||||||
"field":"match[type]"
|
"field": "match[type]"
|
||||||
"op": "=",
|
"op": "=",
|
||||||
"value":"indicator,sighting"
|
"value": "indicator,sighting"
|
||||||
}
|
}
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
query (list): list of filters to extract which ones are TAXII specific
|
query (list): list of filters to extract which ones are TAXII
|
||||||
|
specific.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
params (dict): dict of the TAXII filters but in format required for 'requests.get()'
|
params (dict): dict of the TAXII filters but in format required
|
||||||
|
for 'requests.get()'.
|
||||||
'''
|
"""
|
||||||
|
|
||||||
params = {}
|
params = {}
|
||||||
|
|
||||||
|
@ -234,12 +240,9 @@ class TAXIIDataSource(DataSource):
|
||||||
return params
|
return params
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
'''close down the Data Source - if any clean up is required
|
"""Close down the Data Source - if any clean up is required.
|
||||||
|
|
||||||
'''
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
'''
|
# TODO: - getters/setters (properties) for TAXII config info
|
||||||
TODO:
|
|
||||||
- getters/setters (properties) for TAXII config info
|
|
||||||
'''
|
|
||||||
|
|
Loading…
Reference in New Issue