cti-python-stix2/stix2/sources/__init__.py

421 lines
13 KiB
Python
Raw Normal View History

"""Python STIX 2.0 Sources
2017-05-24 17:25:40 +02:00
2017-10-05 20:45:31 +02:00
.. autosummary::
:toctree: sources
filesystem
filters
memory
taxii
2017-05-24 17:25:40 +02:00
2017-05-26 21:24:33 +02:00
"""
2017-05-24 17:25:40 +02:00
import uuid
from stix2.utils import deduplicate
2017-08-09 20:49:06 +02:00
2017-05-24 17:25:40 +02:00
def make_id():
2017-05-26 21:24:33 +02:00
return str(uuid.uuid4())
2017-05-24 17:25:40 +02:00
2017-07-12 16:58:31 +02:00
class DataStore(object):
"""An implementer will create a concrete subclass from
this class for the specific DataStore.
Args:
source (DataSource): An existing DataSource to use
as this DataStore's DataSource component
sink (DataSink): An existing DataSink to use
as this DataStore's DataSink component
Attributes:
id (str): A unique UUIDv4 to identify this DataStore.
source (DataSource): An object that implements DataSource class.
sink (DataSink): An object that implements DataSink class.
2017-07-12 16:58:31 +02:00
"""
def __init__(self, source=None, sink=None):
self.id = make_id()
2017-08-28 20:32:51 +02:00
self.source = source
self.sink = sink
2017-07-12 16:58:31 +02:00
def get(self, stix_id):
"""Retrieve the most recent version of a single STIX object by ID.
Translate get() call to the appropriate DataSource call.
2017-05-24 17:25:40 +02:00
Args:
stix_id (str): the id of the STIX object to retrieve.
2017-05-24 17:25:40 +02:00
Returns:
stix_obj: the single most recent version of the STIX
object specified by the "id".
2017-05-26 21:24:33 +02:00
"""
return self.source.get(stix_id)
2017-05-24 17:25:40 +02:00
2017-07-12 16:58:31 +02:00
def all_versions(self, stix_id):
"""Retrieve all versions of a single STIX object by ID.
Implement: Translate all_versions() call to the appropriate DataSource call
2017-05-24 17:25:40 +02:00
2017-07-12 16:58:31 +02:00
Args:
stix_id (str): the id of the STIX object to retrieve.
2017-05-24 17:25:40 +02:00
2017-07-12 16:58:31 +02:00
Returns:
stix_objs (list): a list of STIX objects
2017-07-12 16:58:31 +02:00
"""
return self.source.all_versions(stix_id)
2017-05-24 17:25:40 +02:00
2017-07-12 16:58:31 +02:00
def query(self, query):
"""Retrieve STIX objects matching a set of filters.
Implement: Specific data source API calls, processing,
functionality required for retrieving query from the data source.
2017-05-24 17:25:40 +02:00
Args:
2017-07-12 16:58:31 +02:00
query (list): a list of filters (which collectively are the query)
to conduct search on.
2017-05-24 17:25:40 +02:00
2017-07-12 16:58:31 +02:00
Returns:
stix_objs (list): a list of STIX objects
2017-05-24 17:25:40 +02:00
2017-07-12 16:58:31 +02:00
"""
return self.source.query(query=query)
2017-05-24 17:25:40 +02:00
2017-07-12 16:58:31 +02:00
def add(self, stix_objs):
"""Store STIX objects.
Translates add() to the appropriate DataSink call.
2017-05-24 17:25:40 +02:00
Args:
stix_objs (list): a list of STIX objects
2017-07-12 16:58:31 +02:00
"""
return self.sink.add(stix_objs)
2017-05-24 17:25:40 +02:00
2017-07-12 16:58:31 +02:00
class DataSink(object):
"""An implementer will create a concrete subclass from
this class for the specific DataSink.
Attributes:
id (str): A unique UUIDv4 to identify this DataSink.
2017-07-12 16:58:31 +02:00
"""
def __init__(self):
self.id = make_id()
2017-05-24 17:25:40 +02:00
2017-07-12 16:58:31 +02:00
def add(self, stix_objs):
"""Store STIX objects.
Implement: Specific data sink API calls, processing,
functionality required for adding data to the sink
Args:
stix_objs (list): a list of STIX objects (where each object is a
STIX object)
2017-07-12 16:58:31 +02:00
"""
raise NotImplementedError()
2017-05-24 17:25:40 +02:00
2017-07-12 16:58:31 +02:00
class DataSource(object):
"""An implementer will create a concrete subclass from
this class for the specific DataSource.
Attributes:
id (str): A unique UUIDv4 to identify this DataSource.
_filters (set): A collection of filters attached to this DataSource.
2017-07-12 16:58:31 +02:00
"""
def __init__(self):
self.id = make_id()
2017-08-09 20:49:06 +02:00
self.filters = set()
2017-05-24 17:25:40 +02:00
2017-07-12 16:58:31 +02:00
def get(self, stix_id, _composite_filters=None):
"""
Implement: Specific data source API calls, processing,
functionality required for retrieving data from the data source
2017-05-24 17:25:40 +02:00
Args:
2017-07-12 16:58:31 +02:00
stix_id (str): the id of the STIX 2.0 object to retrieve. Should
return a single object, the most recent version of the object
specified by the "id".
2017-05-24 17:25:40 +02:00
_composite_filters (set): set of filters passed from the parent
the CompositeDataSource, not user supplied
2017-05-24 17:25:40 +02:00
2017-07-12 16:58:31 +02:00
Returns:
stix_obj: the STIX object
2017-05-24 17:25:40 +02:00
2017-07-12 16:58:31 +02:00
"""
raise NotImplementedError()
2017-05-24 17:25:40 +02:00
2017-07-12 16:58:31 +02:00
def all_versions(self, stix_id, _composite_filters=None):
"""
Implement: Similar to get() except returns list of all object versions of
the specified "id". In addition, implement the specific data
source API calls, processing, functionality required for retrieving
data from the data source.
2017-05-24 17:25:40 +02:00
Args:
stix_id (str): The id of the STIX 2.0 object to retrieve. Should
2017-07-12 16:58:31 +02:00
return a list of objects, all the versions of the object
specified by the "id".
2017-05-24 17:25:40 +02:00
_composite_filters (set): set of filters passed from the parent
CompositeDataSource, not user supplied
2017-05-24 17:25:40 +02:00
2017-07-12 16:58:31 +02:00
Returns:
stix_objs (list): a list of STIX objects
2017-05-24 17:25:40 +02:00
"""
2017-07-12 16:58:31 +02:00
raise NotImplementedError()
2017-05-24 17:25:40 +02:00
2017-07-12 16:58:31 +02:00
def query(self, query, _composite_filters=None):
"""
Implement:Implement the specific data source API calls, processing,
functionality required for retrieving query from the data source
2017-05-24 17:25:40 +02:00
Args:
2017-07-12 16:58:31 +02:00
query (list): a list of filters (which collectively are the query)
to conduct search on
_composite_filters (set): a set of filters passed from the parent
CompositeDataSource, not user supplied
2017-05-24 17:25:40 +02:00
Returns:
stix_objs (list): a list of STIX objects
2017-05-24 17:25:40 +02:00
2017-05-26 21:24:33 +02:00
"""
2017-07-12 16:58:31 +02:00
raise NotImplementedError()
2017-05-24 17:25:40 +02:00
class CompositeDataSource(DataSource):
"""Controller for all the attached DataSources.
2017-05-24 17:25:40 +02:00
A user can have a single CompositeDataSource as an interface
the a set of DataSources. When an API call is made to the
CompositeDataSource, it is delegated to each of the (real)
DataSources that are attached to it.
2017-05-24 17:25:40 +02:00
DataSources can be attached to CompositeDataSource for a variety
of reasons, e.g. common filters, organization, less API calls.
Attributes:
data_sources (dict): A dictionary of DataSource objects; to be
controlled and used by the Data Source Controller object.
2017-05-24 17:25:40 +02:00
2017-05-26 21:24:33 +02:00
"""
def __init__(self):
2017-09-06 22:20:16 +02:00
"""Create a new STIX Data Source.
2017-05-24 17:25:40 +02:00
2017-07-12 16:58:31 +02:00
Args:
2017-05-24 17:25:40 +02:00
2017-07-12 16:58:31 +02:00
"""
super(CompositeDataSource, self).__init__()
self.data_sources = []
2017-05-24 17:25:40 +02:00
def get(self, stix_id, _composite_filters=None):
"""Retrieve STIX object by STIX ID
2017-05-24 17:25:40 +02:00
Federated retrieve method, iterates through all DataSources
2017-07-12 16:58:31 +02:00
defined in the "data_sources" parameter. Each data source has a
specific API retrieve-like function and associated parameters. This
function does a federated retrieval and consolidation of the data
returned from all the STIX data sources.
2017-05-24 17:25:40 +02:00
A composite data source will pass its attached filters to
each configured data source, pushing filtering to them to handle.
2017-05-24 17:25:40 +02:00
2017-07-12 16:58:31 +02:00
Args:
stix_id (str): the id of the STIX object to retrieve.
2017-05-24 17:25:40 +02:00
_composite_filters (list): a list of filters passed from a
CompositeDataSource (i.e. if this CompositeDataSource is attached
to another parent CompositeDataSource), not user supplied
2017-05-24 17:25:40 +02:00
Returns:
stix_obj: the STIX object to be returned.
2017-05-24 17:25:40 +02:00
2017-05-26 21:24:33 +02:00
"""
if not self.has_data_sources():
2017-09-08 15:01:12 +02:00
raise AttributeError('CompositeDataSource has no data sources')
2017-07-12 16:58:31 +02:00
all_data = []
all_filters = set()
all_filters.update(self.filters)
if _composite_filters:
all_filters.update(_composite_filters)
2017-05-24 17:25:40 +02:00
2017-07-12 16:58:31 +02:00
# for every configured Data Source, call its retrieve handler
for ds in self.data_sources:
data = ds.get(stix_id=stix_id, _composite_filters=all_filters)
if data:
all_data.append(data)
2017-05-24 17:25:40 +02:00
2017-07-12 16:58:31 +02:00
# remove duplicate versions
if len(all_data) > 0:
all_data = deduplicate(all_data)
2017-05-24 17:25:40 +02:00
2017-07-12 16:58:31 +02:00
# reduce to most recent version
2017-08-28 20:32:51 +02:00
stix_obj = sorted(all_data, key=lambda k: k['modified'], reverse=True)[0]
2017-07-12 16:58:31 +02:00
return stix_obj
def all_versions(self, stix_id, _composite_filters=None):
"""Retrieve STIX objects by STIX ID
2017-07-12 16:58:31 +02:00
Federated all_versions retrieve method - iterates through all DataSources
defined in "data_sources"
2017-07-12 16:58:31 +02:00
A composite data source will pass its attached filters to
each configured data source, pushing filtering to them to handle
2017-07-12 16:58:31 +02:00
Args:
stix_id (str): id of the STIX objects to retrieve
2017-05-24 17:25:40 +02:00
_composite_filters (list): a list of filters passed from a
CompositeDataSource (i.e. if this CompositeDataSource is attached
to a parent CompositeDataSource), not user supplied
2017-05-24 17:25:40 +02:00
Returns:
2017-07-12 16:58:31 +02:00
all_data (list): list of STIX objects that have the specified id
2017-05-26 21:24:33 +02:00
"""
if not self.has_data_sources():
2017-09-08 15:01:12 +02:00
raise AttributeError('CompositeDataSource has no data sources')
2017-07-12 16:58:31 +02:00
all_data = []
all_filters = set()
all_filters.update(self.filters)
2017-08-28 20:32:51 +02:00
if _composite_filters:
all_filters.update(_composite_filters)
2017-05-24 17:25:40 +02:00
2017-07-12 16:58:31 +02:00
# retrieve STIX objects from all configured data sources
for ds in self.data_sources:
data = ds.all_versions(stix_id=stix_id, _composite_filters=all_filters)
2017-07-12 16:58:31 +02:00
all_data.extend(data)
2017-05-24 17:25:40 +02:00
2017-07-12 16:58:31 +02:00
# remove exact duplicates (where duplicates are STIX 2.0 objects
# with the same 'id' and 'modified' values)
if len(all_data) > 0:
all_data = deduplicate(all_data)
2017-05-24 17:25:40 +02:00
2017-07-12 16:58:31 +02:00
return all_data
2017-05-24 17:25:40 +02:00
def query(self, query=None, _composite_filters=None):
"""Retrieve STIX objects that match query
Federate the query to all DataSources attached to the
Composite Data Source.
2017-05-24 17:25:40 +02:00
2017-07-12 16:58:31 +02:00
Args:
query (list): list of filters to search on
_composite_filters (list): a list of filters passed from a
CompositeDataSource (i.e. if this CompositeDataSource is attached
to a parent CompositeDataSource), not user supplied
2017-07-12 16:58:31 +02:00
Returns:
all_data (list): list of STIX objects to be returned
2017-05-24 17:25:40 +02:00
2017-05-26 21:24:33 +02:00
"""
if not self.has_data_sources():
2017-09-08 15:01:12 +02:00
raise AttributeError('CompositeDataSource has no data sources')
2017-07-12 16:58:31 +02:00
if not query:
# dont mess with the query (i.e. convert to a set, as thats done
# within the specific DataSources that are called)
2017-07-12 16:58:31 +02:00
query = []
2017-05-24 17:25:40 +02:00
2017-07-12 16:58:31 +02:00
all_data = []
all_filters = set()
all_filters.update(self.filters)
2017-08-28 20:32:51 +02:00
if _composite_filters:
all_filters.update(_composite_filters)
2017-07-12 16:58:31 +02:00
# federate query to all attached data sources,
# pass composite filters to id
for ds in self.data_sources:
data = ds.query(query=query, _composite_filters=all_filters)
2017-07-12 16:58:31 +02:00
all_data.extend(data)
# remove exact duplicates (where duplicates are STIX 2.0
# objects with the same 'id' and 'modified' values)
if len(all_data) > 0:
all_data = deduplicate(all_data)
2017-07-12 16:58:31 +02:00
return all_data
def add_data_source(self, data_source):
"""Attach a DataSource to CompositeDataSource instance
2017-07-12 16:58:31 +02:00
Args:
data_source (DataSource): a stix2.DataSource to attach
to the CompositeDataSource
2017-07-12 16:58:31 +02:00
2017-05-26 21:24:33 +02:00
"""
if issubclass(data_source.__class__, DataSource):
if data_source.id not in [ds_.id for ds_ in self.data_sources]:
# check DataSource not already attached CompositeDataSource
self.data_sources.append(data_source)
else:
raise TypeError("DataSource (to be added) is not of type stix2.DataSource. DataSource type is '%s'" % type(data_source))
return
2017-07-12 16:58:31 +02:00
def add_data_sources(self, data_sources):
"""Attach list of DataSources to CompositeDataSource instance
2017-07-12 16:58:31 +02:00
Args:
data_sources (list): stix2.DataSources to attach to
CompositeDataSource
"""
for ds in data_sources:
self.add_data_source(ds)
2017-07-12 16:58:31 +02:00
return
def remove_data_source(self, data_source_id):
"""Remove DataSource from the CompositeDataSource instance
2017-07-12 16:58:31 +02:00
2017-05-24 17:25:40 +02:00
Args:
data_source_id (str): DataSource IDs.
2017-05-24 17:25:40 +02:00
2017-05-26 21:24:33 +02:00
"""
def _match(ds_id, candidate_ds_id):
return ds_id == candidate_ds_id
self.data_sources[:] = [ds for ds in self.data_sources if not _match(ds.id, data_source_id)]
2017-07-12 16:58:31 +02:00
return
2017-05-24 17:25:40 +02:00
def remove_data_sources(self, data_source_ids):
"""Remove DataSources from the CompositeDataSource instance
Args:
data_source_ids (list): DataSource IDs
2017-07-12 16:58:31 +02:00
"""
for ds_id in data_source_ids:
self.remove_data_source(ds_id)
return
def has_data_sources(self):
return len(self.data_sources)
2017-08-28 20:32:51 +02:00
def get_all_data_sources(self):
return self.data_sources