commit
463d1e6b28
|
@ -7,7 +7,7 @@ from .common import (TLP_AMBER, TLP_GREEN, TLP_RED, TLP_WHITE, CustomMarking,
|
||||||
ExternalReference, GranularMarking, KillChainPhase,
|
ExternalReference, GranularMarking, KillChainPhase,
|
||||||
MarkingDefinition, StatementMarking, TLPMarking)
|
MarkingDefinition, StatementMarking, TLPMarking)
|
||||||
from .core import Bundle, _register_type, parse
|
from .core import Bundle, _register_type, parse
|
||||||
from .environment import ObjectFactory
|
from .environment import Environment, ObjectFactory
|
||||||
from .observables import (URL, AlternateDataStream, ArchiveExt, Artifact,
|
from .observables import (URL, AlternateDataStream, ArchiveExt, Artifact,
|
||||||
AutonomousSystem, CustomObservable, Directory,
|
AutonomousSystem, CustomObservable, Directory,
|
||||||
DomainName, EmailAddress, EmailMessage,
|
DomainName, EmailAddress, EmailMessage,
|
||||||
|
@ -42,6 +42,13 @@ from .patterns import (AndBooleanExpression, AndObservationExpression,
|
||||||
from .sdo import (AttackPattern, Campaign, CourseOfAction, CustomObject,
|
from .sdo import (AttackPattern, Campaign, CourseOfAction, CustomObject,
|
||||||
Identity, Indicator, IntrusionSet, Malware, ObservedData,
|
Identity, Indicator, IntrusionSet, Malware, ObservedData,
|
||||||
Report, ThreatActor, Tool, Vulnerability)
|
Report, ThreatActor, Tool, Vulnerability)
|
||||||
|
from .sources import CompositeDataSource
|
||||||
|
from .sources.filesystem import (FileSystemSink, FileSystemSource,
|
||||||
|
FileSystemStore)
|
||||||
|
from .sources.filters import Filter
|
||||||
|
from .sources.memory import MemorySink, MemorySource, MemoryStore
|
||||||
|
from .sources.taxii import (TAXIICollectionSink, TAXIICollectionSource,
|
||||||
|
TAXIICollectionStore)
|
||||||
from .sro import Relationship, Sighting
|
from .sro import Relationship, Sighting
|
||||||
from .utils import get_dict, new_version, revoke
|
from .utils import get_dict, new_version, revoke
|
||||||
from .version import __version__
|
from .version import __version__
|
||||||
|
|
|
@ -75,13 +75,13 @@ def parse(data, allow_custom=False):
|
||||||
"""Deserialize a string or file-like object into a STIX object.
|
"""Deserialize a string or file-like object into a STIX object.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
data: The STIX 2 string to be parsed.
|
data (str, dict, file-like object): The STIX 2 content to be parsed.
|
||||||
allow_custom (bool): Whether to allow custom properties or not. Default: False.
|
allow_custom (bool): Whether to allow custom properties or not. Default: False.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
An instantiated Python STIX object.
|
An instantiated Python STIX object.
|
||||||
"""
|
|
||||||
|
|
||||||
|
"""
|
||||||
obj = get_dict(data)
|
obj = get_dict(data)
|
||||||
|
|
||||||
if 'type' not in obj:
|
if 'type' not in obj:
|
||||||
|
@ -96,6 +96,6 @@ def parse(data, allow_custom=False):
|
||||||
|
|
||||||
def _register_type(new_type):
|
def _register_type(new_type):
|
||||||
"""Register a custom STIX Object type.
|
"""Register a custom STIX Object type.
|
||||||
"""
|
|
||||||
|
|
||||||
|
"""
|
||||||
OBJ_MAP[new_type._type] = new_type
|
OBJ_MAP[new_type._type] = new_type
|
||||||
|
|
|
@ -1,22 +1,22 @@
|
||||||
import copy
|
import copy
|
||||||
|
|
||||||
|
from .core import parse as _parse
|
||||||
|
from .sources import CompositeDataSource, DataSource, DataStore
|
||||||
|
|
||||||
|
|
||||||
class ObjectFactory(object):
|
class ObjectFactory(object):
|
||||||
"""Object Factory
|
"""Easily create STIX objects with default values for certain properties.
|
||||||
|
|
||||||
Used to easily create STIX objects with default values for certain
|
|
||||||
properties.
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
created_by_ref: Default created_by_ref value to apply to all
|
created_by_ref (optional): Default created_by_ref value to apply to all
|
||||||
objects created by this factory.
|
objects created by this factory.
|
||||||
created: Default created value to apply to all
|
created (optional): Default created value to apply to all
|
||||||
objects created by this factory.
|
objects created by this factory.
|
||||||
external_references: Default `external_references` value to apply
|
external_references (optional): Default `external_references` value to apply
|
||||||
to all objects created by this factory.
|
to all objects created by this factory.
|
||||||
object_marking_refs: Default `object_marking_refs` value to apply
|
object_marking_refs (optional): Default `object_marking_refs` value to apply
|
||||||
to all objects created by this factory.
|
to all objects created by this factory.
|
||||||
list_append: When a default is set for a list property like
|
list_append (bool, optional): When a default is set for a list property like
|
||||||
`external_references` or `object_marking_refs` and a value for
|
`external_references` or `object_marking_refs` and a value for
|
||||||
that property is passed into `create()`, if this is set to True,
|
that property is passed into `create()`, if this is set to True,
|
||||||
that value will be added to the list alongside the default. If
|
that value will be added to the list alongside the default. If
|
||||||
|
@ -44,6 +44,13 @@ class ObjectFactory(object):
|
||||||
self._list_properties = ['external_references', 'object_marking_refs']
|
self._list_properties = ['external_references', 'object_marking_refs']
|
||||||
|
|
||||||
def create(self, cls, **kwargs):
|
def create(self, cls, **kwargs):
|
||||||
|
"""Create a STIX object using object factory defaults.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
cls: the python-stix2 class of the object to be created (eg. Indicator)
|
||||||
|
**kwargs: The property/value pairs of the STIX object to be created
|
||||||
|
"""
|
||||||
|
|
||||||
# Use self.defaults as the base, but update with any explicit args
|
# Use self.defaults as the base, but update with any explicit args
|
||||||
# provided by the user.
|
# provided by the user.
|
||||||
properties = copy.deepcopy(self._defaults)
|
properties = copy.deepcopy(self._defaults)
|
||||||
|
@ -66,3 +73,84 @@ class ObjectFactory(object):
|
||||||
properties.update(**kwargs)
|
properties.update(**kwargs)
|
||||||
|
|
||||||
return cls(**properties)
|
return cls(**properties)
|
||||||
|
|
||||||
|
|
||||||
|
class Environment(object):
|
||||||
|
"""
|
||||||
|
|
||||||
|
Args:
|
||||||
|
factory (ObjectFactory, optional): Factory for creating objects with common
|
||||||
|
defaults for certain properties.
|
||||||
|
store (DataStore, optional): Data store providing the source and sink for the
|
||||||
|
environment.
|
||||||
|
source (DataSource, optional): Source for retrieving STIX objects.
|
||||||
|
sink (DataSink, optional): Destination for saving STIX objects.
|
||||||
|
Invalid if `store` is also provided.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, factory=ObjectFactory(), store=None, source=None, sink=None):
|
||||||
|
self.factory = factory
|
||||||
|
self.source = CompositeDataSource()
|
||||||
|
if store:
|
||||||
|
self.source.add_data_source(store.source)
|
||||||
|
self.sink = store.sink
|
||||||
|
if source:
|
||||||
|
self.source.add_data_source(source)
|
||||||
|
if sink:
|
||||||
|
if store:
|
||||||
|
raise ValueError("Data store already provided! Environment may only have one data sink.")
|
||||||
|
self.sink = sink
|
||||||
|
|
||||||
|
def create(self, *args, **kwargs):
|
||||||
|
return self.factory.create(*args, **kwargs)
|
||||||
|
create.__doc__ = ObjectFactory.create.__doc__
|
||||||
|
|
||||||
|
def get(self, *args, **kwargs):
|
||||||
|
try:
|
||||||
|
return self.source.get(*args, **kwargs)
|
||||||
|
except AttributeError:
|
||||||
|
raise AttributeError('Environment has no data source to query')
|
||||||
|
get.__doc__ = DataStore.get.__doc__
|
||||||
|
|
||||||
|
def all_versions(self, *args, **kwargs):
|
||||||
|
"""Retrieve all versions of a single STIX object by ID.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return self.source.all_versions(*args, **kwargs)
|
||||||
|
except AttributeError:
|
||||||
|
raise AttributeError('Environment has no data source to query')
|
||||||
|
all_versions.__doc__ = DataStore.all_versions.__doc__
|
||||||
|
|
||||||
|
def query(self, *args, **kwargs):
|
||||||
|
"""Retrieve STIX objects matching a set of filters.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return self.source.query(*args, **kwargs)
|
||||||
|
except AttributeError:
|
||||||
|
raise AttributeError('Environment has no data source to query')
|
||||||
|
query.__doc__ = DataStore.query.__doc__
|
||||||
|
|
||||||
|
def add_filters(self, *args, **kwargs):
|
||||||
|
try:
|
||||||
|
return self.source.add_filters(*args, **kwargs)
|
||||||
|
except AttributeError:
|
||||||
|
raise AttributeError('Environment has no data source')
|
||||||
|
add_filters.__doc__ = DataSource.add_filters.__doc__
|
||||||
|
|
||||||
|
def add_filter(self, *args, **kwargs):
|
||||||
|
try:
|
||||||
|
return self.source.add_filter(*args, **kwargs)
|
||||||
|
except AttributeError:
|
||||||
|
raise AttributeError('Environment has no data source')
|
||||||
|
add_filter.__doc__ = DataSource.add_filter.__doc__
|
||||||
|
|
||||||
|
def add(self, *args, **kwargs):
|
||||||
|
try:
|
||||||
|
return self.sink.add(*args, **kwargs)
|
||||||
|
except AttributeError:
|
||||||
|
raise AttributeError('Environment has no data sink to put objects in')
|
||||||
|
add.__doc__ = DataStore.add.__doc__
|
||||||
|
|
||||||
|
def parse(self, *args, **kwargs):
|
||||||
|
return _parse(*args, **kwargs)
|
||||||
|
parse.__doc__ = _parse.__doc__
|
||||||
|
|
|
@ -5,7 +5,7 @@ Classes:
|
||||||
DataStore
|
DataStore
|
||||||
DataSink
|
DataSink
|
||||||
DataSource
|
DataSource
|
||||||
STIXCommonPropertyFilters
|
CompositeDataSource
|
||||||
|
|
||||||
TODO:Test everything
|
TODO:Test everything
|
||||||
|
|
||||||
|
@ -45,30 +45,29 @@ class DataStore(object):
|
||||||
self.sink = sink
|
self.sink = sink
|
||||||
|
|
||||||
def get(self, stix_id):
|
def get(self, stix_id):
|
||||||
"""
|
"""Retrieve the most recent version of a single STIX object by ID.
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
Translate API get() call to the appropriate DataSource call.
|
Translate API get() call to the appropriate DataSource call.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
stix_id (str): the id of the STIX 2.0 object to retrieve. Should
|
stix_id (str): the id of the STIX 2.0 object to retrieve.
|
||||||
return a single object, the most recent version of the object
|
|
||||||
specified by the "id".
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
stix_obj (dictionary): the STIX object to be returned
|
stix_obj (dictionary): the single most recent version of the STIX
|
||||||
|
object specified by the "id".
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return self.source.get(stix_id)
|
return self.source.get(stix_id)
|
||||||
|
|
||||||
def all_versions(self, stix_id):
|
def all_versions(self, stix_id):
|
||||||
"""
|
"""Retrieve all versions of a single STIX object by ID.
|
||||||
|
|
||||||
Implement:
|
Implement:
|
||||||
Translate all_versions() call to the appropriate DataSource call
|
Translate all_versions() call to the appropriate DataSource call
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
stix_id (str): the id of the STIX 2.0 object to retrieve. Should
|
stix_id (str): the id of the STIX 2.0 object to retrieve.
|
||||||
return a single object, the most recent version of the object
|
|
||||||
specified by the "id".
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
stix_objs (list): a list of STIX objects (where each object is a
|
stix_objs (list): a list of STIX objects (where each object is a
|
||||||
|
@ -78,7 +77,8 @@ class DataStore(object):
|
||||||
return self.source.all_versions(stix_id)
|
return self.source.all_versions(stix_id)
|
||||||
|
|
||||||
def query(self, query):
|
def query(self, query):
|
||||||
"""
|
"""Retrieve STIX objects matching a set of filters.
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
Implement the specific data source API calls, processing,
|
Implement the specific data source API calls, processing,
|
||||||
functionality required for retrieving query from the data source.
|
functionality required for retrieving query from the data source.
|
||||||
|
@ -95,10 +95,15 @@ class DataStore(object):
|
||||||
return self.source.query(query=query)
|
return self.source.query(query=query)
|
||||||
|
|
||||||
def add(self, stix_objs):
|
def add(self, stix_objs):
|
||||||
"""
|
"""Store STIX objects.
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
Translate add() to the appropriate DataSink call().
|
Translate add() to the appropriate DataSink call().
|
||||||
|
|
||||||
|
Args:
|
||||||
|
stix_objs (list): a list of STIX objects (where each object is a
|
||||||
|
STIX object)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return self.sink.add(stix_objs)
|
return self.sink.add(stix_objs)
|
||||||
|
|
||||||
|
@ -116,11 +121,16 @@ class DataSink(object):
|
||||||
self.id = make_id()
|
self.id = make_id()
|
||||||
|
|
||||||
def add(self, stix_objs):
|
def add(self, stix_objs):
|
||||||
"""
|
"""Store STIX objects.
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
Implement the specific data sink API calls, processing,
|
Implement the specific data sink API calls, processing,
|
||||||
functionality required for adding data to the sink
|
functionality required for adding data to the sink
|
||||||
|
|
||||||
|
Args:
|
||||||
|
stix_objs (list): a list of STIX objects (where each object is a
|
||||||
|
STIX object)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
@ -201,16 +211,22 @@ class DataSource(object):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def add_filters(self, filters):
|
def add_filters(self, filters):
|
||||||
"""Add multiple filters to the DataSource.
|
"""Add multiple filters to be applied to all queries for STIX objects.
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
for filter in filters:
|
for filter in filters:
|
||||||
self.add_filter(filter)
|
self.add_filter(filter)
|
||||||
|
|
||||||
def add_filter(self, filter):
|
def add_filter(self, filter):
|
||||||
"""Add a filter."""
|
"""Add a filter to be applied to all queries for STIX objects.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
filter: filter to add to the Data Source.
|
||||||
|
|
||||||
|
"""
|
||||||
# check filter field is a supported STIX 2.0 common field
|
# check filter field is a supported STIX 2.0 common field
|
||||||
if filter.field not in STIX_COMMON_FIELDS:
|
if filter.field not in STIX_COMMON_FIELDS:
|
||||||
raise ValueError("Filter 'field' is not a STIX 2.0 common property. Currently only STIX object common properties supported")
|
raise ValueError("Filter 'field' is not a STIX 2.0 common property. Currently only STIX object common properties supported")
|
||||||
|
@ -226,7 +242,7 @@ class DataSource(object):
|
||||||
self.filters.add(filter)
|
self.filters.add(filter)
|
||||||
|
|
||||||
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
|
"""Evaluate 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
|
||||||
|
|
||||||
|
@ -300,11 +316,10 @@ class DataSource(object):
|
||||||
|
|
||||||
|
|
||||||
class CompositeDataSource(DataSource):
|
class CompositeDataSource(DataSource):
|
||||||
"""Composite Data Source
|
"""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 define n Data Sources - creating Data Source (objects)
|
||||||
e.g. a user can define n Data Sources - creating Data Source (objects)
|
for each. There is only one instance of this for any Python STIX 2.0
|
||||||
for each. There is only one instance of this for any python STIX 2.0
|
|
||||||
application.
|
application.
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
|
@ -314,8 +329,7 @@ class CompositeDataSource(DataSource):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""
|
"""Create a new STIX Data Source.
|
||||||
Creates a new STIX Data Source.
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
name (str): A string containing the name to attach in the
|
name (str): A string containing the name to attach in the
|
||||||
|
@ -348,6 +362,9 @@ class CompositeDataSource(DataSource):
|
||||||
stix_obj (dict): the STIX object to be returned.
|
stix_obj (dict): the STIX object to be returned.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
if not self.get_all_data_sources():
|
||||||
|
raise AttributeError('CompositeDataSource has no data sources')
|
||||||
|
|
||||||
all_data = []
|
all_data = []
|
||||||
|
|
||||||
# for every configured Data Source, call its retrieve handler
|
# for every configured Data Source, call its retrieve handler
|
||||||
|
@ -384,6 +401,9 @@ class CompositeDataSource(DataSource):
|
||||||
all_data (list): list of STIX objects that have the specified id
|
all_data (list): list of STIX objects that have the specified id
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
if not self.get_all_data_sources():
|
||||||
|
raise AttributeError('CompositeDataSource has no data sources')
|
||||||
|
|
||||||
all_data = []
|
all_data = []
|
||||||
all_filters = self.filters
|
all_filters = self.filters
|
||||||
|
|
||||||
|
@ -403,9 +423,7 @@ class CompositeDataSource(DataSource):
|
||||||
return all_data
|
return all_data
|
||||||
|
|
||||||
def query(self, query=None, _composite_filters=None):
|
def query(self, query=None, _composite_filters=None):
|
||||||
"""Composite data source query
|
"""Federate the query to all Data Sources attached to the
|
||||||
|
|
||||||
Federate the query to all Data Sources attached to the
|
|
||||||
Composite Data Source.
|
Composite Data Source.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -418,6 +436,9 @@ class CompositeDataSource(DataSource):
|
||||||
all_data (list): list of STIX objects to be returned
|
all_data (list): list of STIX objects to be returned
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
if not self.get_all_data_sources():
|
||||||
|
raise AttributeError('CompositeDataSource has no data sources')
|
||||||
|
|
||||||
if not query:
|
if not query:
|
||||||
query = []
|
query = []
|
||||||
|
|
||||||
|
@ -448,6 +469,8 @@ class CompositeDataSource(DataSource):
|
||||||
to the Composite Data Source
|
to the Composite Data Source
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
if not isinstance(data_sources, list):
|
||||||
|
data_sources = [data_sources]
|
||||||
for ds in data_sources:
|
for ds in data_sources:
|
||||||
if issubclass(ds.__class__, DataSource):
|
if issubclass(ds.__class__, DataSource):
|
||||||
if ds.id in self.data_sources:
|
if ds.id in self.data_sources:
|
||||||
|
|
|
@ -22,33 +22,22 @@ import collections
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from stix2validator import validate_instance
|
|
||||||
|
|
||||||
from stix2 import Bundle
|
from stix2 import Bundle
|
||||||
from stix2.sources import DataSink, DataSource, DataStore
|
from stix2.sources import DataSink, DataSource, DataStore
|
||||||
from stix2.sources.filters import Filter
|
from stix2.sources.filters import Filter
|
||||||
|
|
||||||
|
|
||||||
def _add(store, stix_data):
|
def _add(store, stix_data=None):
|
||||||
"""Adds stix objects to MemoryStore/Source/Sink."""
|
"""Adds stix objects to MemoryStore/Source/Sink."""
|
||||||
if isinstance(stix_data, collections.Mapping):
|
if isinstance(stix_data, collections.Mapping):
|
||||||
# stix objects are in a bundle
|
# stix objects are in a bundle
|
||||||
# verify STIX json data
|
|
||||||
r = validate_instance(stix_data)
|
|
||||||
# make dictionary of the objects for easy lookup
|
# make dictionary of the objects for easy lookup
|
||||||
if r.is_valid:
|
|
||||||
for stix_obj in stix_data["objects"]:
|
for stix_obj in stix_data["objects"]:
|
||||||
store.data[stix_obj["id"]] = stix_obj
|
store.data[stix_obj["id"]] = stix_obj
|
||||||
else:
|
|
||||||
raise ValueError("Error: data passed was found to not be valid by the STIX 2 Validator: \n%s", r.as_dict())
|
|
||||||
elif isinstance(stix_data, list):
|
elif isinstance(stix_data, list):
|
||||||
# stix objects are in a list
|
# stix objects are in a list
|
||||||
for stix_obj in stix_data:
|
for stix_obj in stix_data:
|
||||||
r = validate_instance(stix_obj)
|
|
||||||
if r.is_valid:
|
|
||||||
store.data[stix_obj["id"]] = stix_obj
|
store.data[stix_obj["id"]] = stix_obj
|
||||||
else:
|
|
||||||
raise ValueError("Error: STIX object %s is not valid under STIX 2 validator.\n%s", stix_obj["id"], r)
|
|
||||||
else:
|
else:
|
||||||
raise ValueError("stix_data must be in bundle format or raw list")
|
raise ValueError("stix_data must be in bundle format or raw list")
|
||||||
|
|
||||||
|
@ -56,7 +45,7 @@ def _add(store, stix_data):
|
||||||
class MemoryStore(DataStore):
|
class MemoryStore(DataStore):
|
||||||
"""
|
"""
|
||||||
"""
|
"""
|
||||||
def __init__(self, stix_data):
|
def __init__(self, stix_data=None):
|
||||||
"""
|
"""
|
||||||
Notes:
|
Notes:
|
||||||
It doesn't make sense to create a MemoryStore by passing
|
It doesn't make sense to create a MemoryStore by passing
|
||||||
|
@ -83,7 +72,7 @@ class MemoryStore(DataStore):
|
||||||
class MemorySink(DataSink):
|
class MemorySink(DataSink):
|
||||||
"""
|
"""
|
||||||
"""
|
"""
|
||||||
def __init__(self, stix_data, _store=False):
|
def __init__(self, stix_data=None, _store=False):
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
stix_data (dictionary OR list): valid STIX 2.0 content in
|
stix_data (dictionary OR list): valid STIX 2.0 content in
|
||||||
|
@ -114,7 +103,7 @@ class MemorySink(DataSink):
|
||||||
|
|
||||||
class MemorySource(DataSource):
|
class MemorySource(DataSource):
|
||||||
|
|
||||||
def __init__(self, stix_data, _store=False):
|
def __init__(self, stix_data=None, _store=False):
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
stix_data (dictionary OR list): valid STIX 2.0 content in
|
stix_data (dictionary OR list): valid STIX 2.0 content in
|
||||||
|
@ -193,10 +182,5 @@ class MemorySource(DataSource):
|
||||||
file_path = os.path.abspath(file_path)
|
file_path = os.path.abspath(file_path)
|
||||||
stix_data = json.load(open(file_path, "r"))
|
stix_data = json.load(open(file_path, "r"))
|
||||||
|
|
||||||
r = validate_instance(stix_data)
|
|
||||||
|
|
||||||
if r.is_valid:
|
|
||||||
for stix_obj in stix_data["objects"]:
|
for stix_obj in stix_data["objects"]:
|
||||||
self.data[stix_obj["id"]] = stix_obj
|
self.data[stix_obj["id"]] = stix_obj
|
||||||
|
|
||||||
raise ValueError("Error: STIX data loaded from file (%s) was found to not be validated by STIX 2 Validator.\n%s", file_path, r)
|
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
import stix2
|
import stix2
|
||||||
|
|
||||||
from .constants import (FAKE_TIME, IDENTITY_ID, IDENTITY_KWARGS,
|
from .constants import (FAKE_TIME, IDENTITY_ID, IDENTITY_KWARGS, INDICATOR_ID,
|
||||||
INDICATOR_KWARGS)
|
INDICATOR_KWARGS, MALWARE_ID)
|
||||||
|
|
||||||
|
|
||||||
def test_object_factory_created_by_ref_str():
|
def test_object_factory_created_by_ref_str():
|
||||||
|
@ -81,3 +83,106 @@ def test_object_factory_list_replace():
|
||||||
ind = factory.create(stix2.Indicator, external_references=ext_ref2, **INDICATOR_KWARGS)
|
ind = factory.create(stix2.Indicator, external_references=ext_ref2, **INDICATOR_KWARGS)
|
||||||
assert len(ind.external_references) == 1
|
assert len(ind.external_references) == 1
|
||||||
assert ind.external_references[0].source_name == "Yet Another Threat Report"
|
assert ind.external_references[0].source_name == "Yet Another Threat Report"
|
||||||
|
|
||||||
|
|
||||||
|
def test_environment_functions():
|
||||||
|
env = stix2.Environment(stix2.ObjectFactory(created_by_ref=IDENTITY_ID),
|
||||||
|
stix2.MemoryStore())
|
||||||
|
|
||||||
|
# Create a STIX object
|
||||||
|
ind = env.create(stix2.Indicator, id=INDICATOR_ID, **INDICATOR_KWARGS)
|
||||||
|
assert ind.created_by_ref == IDENTITY_ID
|
||||||
|
|
||||||
|
# Add objects to datastore
|
||||||
|
ind2 = ind.new_version(labels=['benign'])
|
||||||
|
env.add([ind, ind2])
|
||||||
|
|
||||||
|
# Get both versions of the object
|
||||||
|
resp = env.all_versions(INDICATOR_ID)
|
||||||
|
assert len(resp) == 1 # should be 2, but MemoryStore only keeps 1 version of objects
|
||||||
|
|
||||||
|
# Get just the most recent version of the object
|
||||||
|
resp = env.get(INDICATOR_ID)
|
||||||
|
assert resp['labels'][0] == 'benign'
|
||||||
|
|
||||||
|
# Search on something other than id
|
||||||
|
query = [stix2.Filter('type', '=', 'vulnerability')]
|
||||||
|
resp = env.query(query)
|
||||||
|
assert len(resp) == 0
|
||||||
|
|
||||||
|
# See different results after adding filters to the environment
|
||||||
|
env.add_filters([stix2.Filter('type', '=', 'indicator'),
|
||||||
|
stix2.Filter('created_by_ref', '=', IDENTITY_ID)])
|
||||||
|
env.add_filter(stix2.Filter('labels', '=', 'benign')) # should be 'malicious-activity'
|
||||||
|
resp = env.get(INDICATOR_ID)
|
||||||
|
assert resp['labels'][0] == 'benign' # should be 'malicious-activity'
|
||||||
|
|
||||||
|
|
||||||
|
def test_environment_source_and_sink():
|
||||||
|
ind = stix2.Indicator(id=INDICATOR_ID, **INDICATOR_KWARGS)
|
||||||
|
env = stix2.Environment(source=stix2.MemorySource([ind]), sink=stix2.MemorySink([ind]))
|
||||||
|
assert env.get(INDICATOR_ID).labels[0] == 'malicious-activity'
|
||||||
|
|
||||||
|
|
||||||
|
def test_environment_datastore_and_sink():
|
||||||
|
with pytest.raises(ValueError) as excinfo:
|
||||||
|
stix2.Environment(factory=stix2.ObjectFactory(),
|
||||||
|
store=stix2.MemoryStore(), sink=stix2.MemorySink)
|
||||||
|
assert 'Data store already provided' in str(excinfo.value)
|
||||||
|
|
||||||
|
|
||||||
|
def test_environment_no_datastore():
|
||||||
|
env = stix2.Environment(factory=stix2.ObjectFactory())
|
||||||
|
|
||||||
|
with pytest.raises(AttributeError) as excinfo:
|
||||||
|
env.add(stix2.Indicator(**INDICATOR_KWARGS))
|
||||||
|
assert 'Environment has no data sink to put objects in' in str(excinfo.value)
|
||||||
|
|
||||||
|
with pytest.raises(AttributeError) as excinfo:
|
||||||
|
env.get(INDICATOR_ID)
|
||||||
|
assert 'Environment has no data source' in str(excinfo.value)
|
||||||
|
|
||||||
|
with pytest.raises(AttributeError) as excinfo:
|
||||||
|
env.all_versions(INDICATOR_ID)
|
||||||
|
assert 'Environment has no data source' in str(excinfo.value)
|
||||||
|
|
||||||
|
with pytest.raises(AttributeError) as excinfo:
|
||||||
|
env.query(INDICATOR_ID)
|
||||||
|
assert 'Environment has no data source' in str(excinfo.value)
|
||||||
|
|
||||||
|
with pytest.raises(AttributeError) as excinfo:
|
||||||
|
env.add_filters(INDICATOR_ID)
|
||||||
|
assert 'Environment has no data source' in str(excinfo.value)
|
||||||
|
|
||||||
|
with pytest.raises(AttributeError) as excinfo:
|
||||||
|
env.add_filter(INDICATOR_ID)
|
||||||
|
assert 'Environment has no data source' in str(excinfo.value)
|
||||||
|
|
||||||
|
|
||||||
|
def test_environment_datastore_and_no_object_factory():
|
||||||
|
# Uses a default object factory
|
||||||
|
env = stix2.Environment(store=stix2.MemoryStore())
|
||||||
|
ind = env.create(stix2.Indicator, id=INDICATOR_ID, **INDICATOR_KWARGS)
|
||||||
|
assert ind.id == INDICATOR_ID
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_malware():
|
||||||
|
env = stix2.Environment()
|
||||||
|
data = """{
|
||||||
|
"type": "malware",
|
||||||
|
"id": "malware--fedcba98-7654-3210-fedc-ba9876543210",
|
||||||
|
"created": "2017-01-01T12:34:56.000Z",
|
||||||
|
"modified": "2017-01-01T12:34:56.000Z",
|
||||||
|
"name": "Cryptolocker",
|
||||||
|
"labels": [
|
||||||
|
"ransomware"
|
||||||
|
]
|
||||||
|
}"""
|
||||||
|
mal = env.parse(data)
|
||||||
|
|
||||||
|
assert mal.type == 'malware'
|
||||||
|
assert mal.id == MALWARE_ID
|
||||||
|
assert mal.created == FAKE_TIME
|
||||||
|
assert mal.modified == FAKE_TIME
|
||||||
|
assert mal.labels == ['ransomware']
|
||||||
|
assert mal.name == "Cryptolocker"
|
||||||
|
|
Loading…
Reference in New Issue