diff --git a/stix2/base.py b/stix2/base.py index d59395c..acac94c 100644 --- a/stix2/base.py +++ b/stix2/base.py @@ -81,7 +81,7 @@ class _STIXBase(collections.Mapping): def _check_object_constraints(self): for m in self.get("granular_markings", []): - validate(self, m.get("selectors"), m.get("marking_ref")) + validate(self, m.get("selectors")) def __init__(self, allow_custom=False, **kwargs): cls = self.__class__ diff --git a/stix2/exceptions.py b/stix2/exceptions.py index 864dec8..63ac921 100644 --- a/stix2/exceptions.py +++ b/stix2/exceptions.py @@ -159,7 +159,7 @@ class ParseError(STIXError, ValueError): super(ParseError, self).__init__(msg) -class InvalidSelectorError(STIXError, ValueError): +class InvalidSelectorError(STIXError, AssertionError): """Granular Marking selector violation. The selector must resolve into an existing STIX object property.""" def __init__(self, cls, key): @@ -168,31 +168,18 @@ class InvalidSelectorError(STIXError, ValueError): self.key = key def __str__(self): - msg = "Selector '{0}' in '{1}' is not valid!" - return msg.format(self.key, self.__class__.__name__) + msg = "Selector {0} in {1} is not valid!" + return msg.format(self.key, self.cls.__class__.__name__) -class InvalidMarkingError(STIXError, ValueError): - """Marking violation. The marking reference must be a valid identifier.""" +class MarkingNotFoundError(STIXError, AssertionError): + """Marking violation. The marking reference must be present in SDO or SRO.""" def __init__(self, cls, key): - super(InvalidMarkingError, self).__init__() + super(MarkingNotFoundError, self).__init__() self.cls = cls self.key = key def __str__(self): - msg = "Marking '{0}' in '{1}' is not a valid marking reference." - return msg.format(self.key, self.__class__.__name__) - - -class DuplicateMarkingError(STIXError, ValueError): - """Marking violation. The marking reference is a duplicate.""" - - def __init__(self, cls, key): - super(DuplicateMarkingError, self).__init__() - self.cls = cls - self.key = key - - def __str__(self): - msg = "Marking '{0}' in '{1}' is a duplicate marking reference." - return msg.format(self.key, self.__class__.__name__) + msg = "Marking {0} was not found in {1}!" + return msg.format(self.key, self.cls.__class__.__name__) diff --git a/stix2/markings/__init__.py b/stix2/markings/__init__.py index f158723..4ef3e3a 100644 --- a/stix2/markings/__init__.py +++ b/stix2/markings/__init__.py @@ -13,16 +13,16 @@ def get_markings(obj, selectors, inherited=False, descendants=False): Get all markings associated to the field(s). Args: - obj: A TLO object. - selectors: string or list of selectors strings relative to the TLO in - which the field(s) appear(s). + obj: An SDO or SRO object. + selectors: string or list of selectors strings relative to the SDO or + SRO in which the properties appear. inherited: If True, include object level markings and granular markings - inherited relative to the field(s). + inherited relative to the properties. descendants: If True, include granular markings applied to any children - relative to the field(s). + relative to the properties. Returns: - list: Marking IDs that matched the selectors expression. + list: Marking identifiers that matched the selectors expression. Note: If ``selectors`` is None, operation will be performed only on object @@ -51,11 +51,15 @@ def set_markings(obj, selectors, marking): marking. Refer to `clear_markings` and `add_markings` for details. Args: - obj: A TLO object. - selectors: string or list of selectors strings relative to the TLO in - which the field(s) appear(s). + obj: An SDO or SRO object. + selectors: string or list of selectors strings relative to the SDO or + SRO in which the properties appear. marking: identifier or list of marking identifiers that apply to the - field(s) selected by `selectors`. + properties selected by `selectors`. + + Returns: + A new version of the given SDO or SRO with specified markings removed + and new ones added. Note: If ``selectors`` is None, operations will be performed on object level @@ -73,15 +77,19 @@ def remove_markings(obj, selectors, marking): Removes granular_marking from the granular_markings collection. Args: - obj: A TLO object. - selectors: string or list of selectors strings relative to the TLO in - which the field(s) appear(s). + obj: An SDO or SRO object. + selectors: string or list of selectors strings relative to the SDO or + SRO in which the properties appear. marking: identifier or list of marking identifiers that apply to the - field(s) selected by `selectors`. + properties selected by `selectors`. Raises: - AssertionError: If `selectors` or `marking` fail data validation. Also - if markings to remove are not found on the provided TLO. + InvalidSelectorError: If `selectors` fail validation. + MarkingNotFoundError: If markings to remove are not found on + the provided SDO or SRO. + + Returns: + A new version of the given SDO or SRO with specified markings removed. Note: If ``selectors`` is None, operations will be performed on object level @@ -99,14 +107,17 @@ def add_markings(obj, selectors, marking): Appends a granular_marking to the granular_markings collection. Args: - obj: A TLO object. - selectors: string or list of selectors strings relative to the TLO in - which the field(s) appear(s). + obj: An SDO or SRO object. + selectors: string or list of selectors strings relative to the SDO or + SRO in which the properties appear. marking: identifier or list of marking identifiers that apply to the - field(s) selected by `selectors`. + properties selected by `selectors`. Raises: - AssertionError: If `selectors` or `marking` fail data validation. + InvalidSelectorError: If `selectors` fail validation. + + Returns: + A new version of the given SDO or SRO with specified markings added. Note: If ``selectors`` is None, operations will be performed on object level @@ -124,9 +135,17 @@ def clear_markings(obj, selectors): Removes all granular_marking associated with the selectors. Args: - obj: A TLO object. - selectors: string or list of selectors strings relative to the TLO in - which the field(s) appear(s). + obj: An SDO or SRO object. + selectors: string or list of selectors strings relative to the SDO or + SRO in which the field(s) appear(s). + + Raises: + InvalidSelectorError: If `selectors` fail validation. + MarkingNotFoundError: If markings to remove are not found on + the provided SDO or SRO. + + Returns: + A new version of the given SDO or SRO with specified markings cleared. Note: If ``selectors`` is None, operations will be performed on object level @@ -144,23 +163,23 @@ def is_marked(obj, selectors, marking=None, inherited=False, descendants=False): Checks if field(s) is marked by any marking or by specific marking(s). Args: - obj: A TLO object. - selectors: string or list of selectors strings relative to the TLO in - which the field(s) appear(s). + obj: An SDO or SRO object. + selectors: string or list of selectors strings relative to the SDO or + SRO in which the field(s) appear(s). marking: identifier or list of marking identifiers that apply to the - field(s) selected by `selectors`. + properties selected by `selectors`. inherited: If True, include object level markings and granular markings - inherited to determine if the field(s) is/are marked. + inherited to determine if the properties is/are marked. descendants: If True, include granular markings applied to any children - of the given selector to determine if the field(s) is/are marked. + of the given selector to determine if the properties is/are marked. Returns: - bool: True if ``selectors`` is found on internal TLO collection. + bool: True if ``selectors`` is found on internal SDO or SRO collection. False otherwise. Note: - When a list of marking IDs is provided, if ANY of the provided marking - IDs matches, True is returned. + When a list of marking identifiers is provided, if ANY of the provided + marking identifiers match, True is returned. If ``selectors`` is None, operation will be performed only on object level markings. diff --git a/stix2/markings/granular_markings.py b/stix2/markings/granular_markings.py index 5c223a3..6f2933f 100644 --- a/stix2/markings/granular_markings.py +++ b/stix2/markings/granular_markings.py @@ -1,24 +1,29 @@ +from stix2 import exceptions from stix2.markings import utils def get_markings(obj, selectors, inherited=False, descendants=False): """ - Get all markings associated to the field(s). + Get all markings associated to with the properties. Args: - obj: A TLO object. - selectors: string or list of selector strings relative to the TLO in - which the field(s) appear(s). - inherited: If True, include markings inherited relative to the field(s). + obj: An SDO or SRO object. + selectors: string or list of selector strings relative to the SDO or + SRO in which the properties appear. + inherited: If True, include markings inherited relative to the + properties. descendants: If True, include granular markings applied to any children - relative to the field(s). + relative to the properties. + + Raises: + InvalidSelectorError: If `selectors` fail validation. Returns: - list: Marking IDs that matched the selectors expression. + list: Marking identifiers that matched the selectors expression. """ - selectors = utils.fix_value(selectors) + selectors = utils.convert_to_list(selectors) utils.validate(obj, selectors) granular_markings = obj.get("granular_markings", []) @@ -46,11 +51,15 @@ def set_markings(obj, selectors, marking): marking. Refer to `clear_markings` and `add_markings` for details. Args: - obj: A TLO object. - selectors: string or list of selector strings relative to the TLO in - which the field(s) appear(s). + obj: An SDO or SRO object. + selectors: string or list of selector strings relative to the SDO or + SRO in which the properties appear. marking: identifier or list of marking identifiers that apply to the - field(s) selected by `selectors`. + properties selected by `selectors`. + + Returns: + A new version of the given SDO or SRO with specified markings removed + and new ones added. """ obj = clear_markings(obj, selectors) @@ -62,19 +71,23 @@ def remove_markings(obj, selectors, marking): Removes granular_marking from the granular_markings collection. Args: - obj: A TLO object. - selectors: string or list of selectors strings relative to the TLO in - which the field(s) appear(s). + obj: An SDO or SRO object. + selectors: string or list of selectors strings relative to the SDO or + SRO in which the properties appear. marking: identifier or list of marking identifiers that apply to the - field(s) selected by `selectors`. + properties selected by `selectors`. Raises: - AssertionError: If `selectors` or `marking` fail data validation. Also - if markings to remove are not found on the provided TLO. + InvalidSelectorError: If `selectors` fail validation. + MarkingNotFoundError: If markings to remove are not found on + the provided SDO or SRO. + + Returns: + A new version of the given SDO or SRO with specified markings removed. """ - selectors = utils.fix_value(selectors) - utils.validate(obj, selectors, marking) + selectors = utils.convert_to_list(selectors) + utils.validate(obj, selectors) granular_markings = obj.get("granular_markings") @@ -90,14 +103,10 @@ def remove_markings(obj, selectors, marking): else: to_remove = [{"marking_ref": marking, "selectors": selectors}] - to_remove = utils.expand_markings(to_remove) - tlo = utils.build_granular_marking(to_remove) - - remove = tlo.get("granular_markings", []) + remove = utils.build_granular_marking(to_remove).get("granular_markings") if not any(marking in granular_markings for marking in remove): - raise AssertionError("Unable to remove Granular Marking(s) from" - " internal collection. Marking(s) not found...") + raise exceptions.MarkingNotFoundError(obj, remove) granular_markings = [ m for m in granular_markings if m not in remove @@ -105,10 +114,10 @@ def remove_markings(obj, selectors, marking): granular_markings = utils.compress_markings(granular_markings) - if not granular_markings: - return obj.new_version(granular_markings=None) - else: + if granular_markings: return obj.new_version(granular_markings=granular_markings) + else: + return obj.new_version(granular_markings=None) def add_markings(obj, selectors, marking): @@ -116,18 +125,21 @@ def add_markings(obj, selectors, marking): Appends a granular_marking to the granular_markings collection. Args: - obj: A TLO object. + obj: An SDO or SRO object. selectors: list of type string, selectors must be relative to the TLO in which the properties appear. - marking: identifier that apply to the properties selected by - `selectors`. + marking: identifier or list of marking identifiers that apply to the + properties selected by `selectors`. Raises: - AssertionError: If `selectors` or `marking` fail data validation. + InvalidSelectorError: If `selectors` fail validation. + + Returns: + A new version of the given SDO or SRO with specified markings added. """ - selectors = utils.fix_value(selectors) - utils.validate(obj, selectors, marking) + selectors = utils.convert_to_list(selectors) + utils.validate(obj, selectors) if isinstance(marking, list): granular_marking = [] @@ -149,16 +161,20 @@ def clear_markings(obj, selectors): Removes all granular_markings associated with the selectors. Args: - obj: A TLO object. - selectors: string or list of selectors strings relative to the TLO in - which the field(s) appear(s). + obj: An SDO or SRO object. + selectors: string or list of selectors strings relative to the SDO or + SRO in which the properties appear. Raises: - AssertionError: If `selectors` or `marking` fail data validation. Also - if markings to remove are not found on the provided TLO. + InvalidSelectorError: If `selectors` fail validation. + MarkingNotFoundError: If markings to remove are not found on + the provided SDO or SRO. + + Returns: + A new version of the given SDO or SRO with specified markings cleared. """ - selectors = utils.fix_value(selectors) + selectors = utils.convert_to_list(selectors) utils.validate(obj, selectors) granular_markings = obj.get("granular_markings") @@ -179,8 +195,7 @@ def clear_markings(obj, selectors): for clear_marking in clear for clear_selector in clear_marking.get("selectors", []) ): - raise AssertionError("Unable to clear Granular Marking(s) from" - " internal collection. Selector(s) not found...") + raise exceptions.MarkingNotFoundError(obj, clear) for granular_marking in granular_markings: for s in selectors: @@ -192,10 +207,10 @@ def clear_markings(obj, selectors): granular_markings = utils.compress_markings(granular_markings) - if not granular_markings: - return obj.new_version(granular_markings=None) - else: + if granular_markings: return obj.new_version(granular_markings=granular_markings) + else: + return obj.new_version(granular_markings=None) def is_marked(obj, selectors, marking=None, inherited=False, descendants=False): @@ -203,27 +218,30 @@ def is_marked(obj, selectors, marking=None, inherited=False, descendants=False): Checks if field is marked by any marking or by specific marking(s). Args: - obj: A TLO object. - selectors: string or list of selectors strings relative to the TLO in - which the field(s) appear(s). + obj: An SDO or SRO object. + selectors: string or list of selectors strings relative to the SDO or + SRO in which the properties appear. marking: identifier or list of marking identifiers that apply to the - field(s) selected by `selectors`. + properties selected by `selectors`. inherited: If True, return markings inherited from the given selector. descendants: If True, return granular markings applied to any children of the given selector. + Raises: + InvalidSelectorError: If `selectors` fail validation. + Returns: - bool: True if ``selectors`` is found on internal TLO collection. + bool: True if ``selectors`` is found on internal SDO or SRO collection. False otherwise. Note: - When a list of marking IDs is provided, if ANY of the provided marking - IDs matches, True is returned. + When a list of marking identifiers is provided, if ANY of the provided + marking identifiers match, True is returned. """ - selectors = utils.fix_value(selectors) - marking = utils.fix_value(marking) - utils.validate(obj, selectors, marking) + selectors = utils.convert_to_list(selectors) + marking = utils.convert_to_list(marking) + utils.validate(obj, selectors) granular_markings = obj.get("granular_markings", []) diff --git a/stix2/markings/object_markings.py b/stix2/markings/object_markings.py index c0d6d72..a3b2586 100644 --- a/stix2/markings/object_markings.py +++ b/stix2/markings/object_markings.py @@ -1,4 +1,5 @@ +from stix2 import exceptions from stix2.markings import utils @@ -10,7 +11,7 @@ def get_markings(obj): obj: A SDO or SRO object. Returns: - list: Marking IDs contained in the SDO or SRO. Empty list if no + list: Marking identifiers contained in the SDO or SRO. Empty list if no markings are present in `object_marking_refs`. """ @@ -25,13 +26,12 @@ def add_markings(obj, marking): obj: A SDO or SRO object. marking: identifier or list of identifiers to apply SDO or SRO object. - Raises: - AssertionError: If `marking` fail data validation. + Returns: + A new version of the given SDO or SRO with specified markings added. """ marking = utils.convert_to_list(marking) - # TODO: Remove set for comparison and raise DuplicateMarkingException. object_markings = set(obj.get("object_marking_refs", []) + marking) return obj.new_version(object_marking_refs=list(object_markings)) @@ -47,12 +47,14 @@ def remove_markings(obj, marking): SDO or SRO object. Raises: - AssertionError: If markings to remove are not found on the provided - SDO or SRO. + MarkingNotFoundError: If markings to remove are not found on + the provided SDO or SRO. + + Returns: + A new version of the given SDO or SRO with specified markings removed. """ marking = utils.convert_to_list(marking) - utils.validate(obj, marking=marking) object_markings = obj.get("object_marking_refs", []) @@ -60,8 +62,7 @@ def remove_markings(obj, marking): return obj if any(x not in obj["object_marking_refs"] for x in marking): - raise AssertionError("Unable to remove Object Level Marking(s) from " - "internal collection. Marking(s) not found...") + raise exceptions.MarkingNotFoundError(obj, marking) new_markings = [x for x in object_markings if x not in marking] if new_markings: @@ -80,6 +81,10 @@ def set_markings(obj, marking): marking: identifier or list of identifiers to apply in the SDO or SRO object. + Returns: + A new version of the given SDO or SRO with specified markings removed + and new ones added. + """ return add_markings(clear_markings(obj), marking) @@ -91,6 +96,9 @@ def clear_markings(obj): Args: obj: A SDO or SRO object. + Returns: + A new version of the given SDO or SRO with object_marking_refs cleared. + """ return obj.new_version(object_marking_refs=None) diff --git a/stix2/markings/utils.py b/stix2/markings/utils.py index adf3069..4dcbf00 100644 --- a/stix2/markings/utils.py +++ b/stix2/markings/utils.py @@ -3,9 +3,23 @@ import collections import six +from stix2 import exceptions -def evaluate_expression(obj, selector): +def _evaluate_expression(obj, selector): + """ + Walks an SDO or SRO generating selectors to match against ``selector``. If + a match is found and the the value of this property is present in the + objects. Matching value of the property will be returned. + + Args: + obj: An SDO or SRO object. + selector: A string following the selector syntax. + + Returns: + list: Values contained in matching property. Otherwise empty list. + + """ for items, value in iterpath(obj): path = ".".join(items) @@ -15,45 +29,26 @@ def evaluate_expression(obj, selector): return [] -def validate_selector(obj, selector): - results = list(evaluate_expression(obj, selector)) +def _validate_selector(obj, selector): + results = list(_evaluate_expression(obj, selector)) if len(results) >= 1: return True -def validate_markings(marking): - if isinstance(marking, six.string_types): - if not marking: - return False - else: - return True - - elif isinstance(marking, list) and len(marking) >= 1: - for m in marking: - if not m: - return False - elif not isinstance(m, six.string_types): - return False - - return True - else: - return False - - -def validate(obj, selectors=None, marking=None): - - if selectors is not None: - assert selectors - +def validate(obj, selectors): + """Given an SDO or SRO, check that each selector is valid.""" + if selectors: for s in selectors: - assert validate_selector(obj, s) + if not _validate_selector(obj, s): + raise exceptions.InvalidSelectorError(obj, s) + return - if marking is not None: - assert validate_markings(marking) + raise exceptions.InvalidSelectorError(obj, selectors) def convert_to_list(data): + """Convert input into a list for further processing.""" if data is not None: if isinstance(data, list): return data @@ -61,14 +56,47 @@ def convert_to_list(data): return [data] -def fix_value(data): - data = convert_to_list(data) - - return data - - def compress_markings(granular_markings): + """ + Compress granular markings list. If there is more than one marking + identifier matches. It will collapse into a single granular marking. + Examples: + Input: + [ + { + "selectors": [ + "description" + ], + "marking_ref": "marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9" + }, + { + "selectors": [ + "name" + ], + "marking_ref": "marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9" + } + ] + + Output: + [ + { + "selectors": [ + "description", + "name" + ], + "marking_ref": "marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9" + } + ] + + Args: + granular_markings: The granular markings list property present in a + SDO or SRO. + + Returns: + list: A list with all markings collapsed. + + """ if not granular_markings: return @@ -88,10 +116,46 @@ def compress_markings(granular_markings): def expand_markings(granular_markings): + """ + Expands granular markings list. If there is more than one selector per + granular marking. It will be expanded using the same marking_ref. - if not granular_markings: - return + Examples: + Input: + [ + { + "selectors": [ + "description", + "name" + ], + "marking_ref": "marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9" + } + ] + Output: + [ + { + "selectors": [ + "description" + ], + "marking_ref": "marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9" + }, + { + "selectors": [ + "name" + ], + "marking_ref": "marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9" + } + ] + + Args: + granular_markings: The granular markings list property present in a + SDO or SRO. + + Returns: + list: A list with all markings expanded. + + """ expanded = [] for marking in granular_markings: @@ -109,11 +173,9 @@ def expand_markings(granular_markings): def build_granular_marking(granular_marking): - tlo = {"granular_markings": granular_marking} - - expand_markings(tlo["granular_markings"]) - - return tlo + """Returns a dictionary with the required structure for a granular + marking""" + return {"granular_markings": expand_markings(granular_marking)} def iterpath(obj, path=None): @@ -122,7 +184,7 @@ def iterpath(obj, path=None): tuple containing a list of ancestors and the property value. Args: - obj: A TLO object. + obj: An SDO or SRO object. path: None, used recursively to store ancestors. Example: @@ -164,36 +226,3 @@ def iterpath(obj, path=None): path.pop() path.pop() - - -def get_selector(obj, prop): - """ - Function that creates a selector based on ``prop``. - - Args: - obj: A TLO object. - prop: A property of the TLO object. - - Note: - Must supply the actual value inside the structure. Since some - limitations exist with Python interning methods, checking for object - location is for now the option to assert the data. - - Example: - >>> selector = get_selector(obj, obj["cybox"]["objects"][0]["file_name"]) - >>> print(selector) - ["cybox.objects.[0].file_name"] - - Returns: - list: A list with one selector that asserts the supplied property. - Empty list if it was unable to find the property. - - """ - selector = [] - - for ancestors, value in iterpath(obj): - if value is prop: - path = ".".join(ancestors) - selector.append(path) - - return selector diff --git a/stix2/test/test_markings.py b/stix2/test/test_markings.py index 63aecc3..a253a1b 100644 --- a/stix2/test/test_markings.py +++ b/stix2/test/test_markings.py @@ -102,7 +102,7 @@ def test_marking_def_invalid_type(): stix2.MarkingDefinition( id="marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9", created="2017-01-20T00:00:00.000Z", - definition_type="my-definiition-type", + definition_type="my-definition-type", definition=stix2.StatementMarking("Copyright 2016, Example Corp") )