diff --git a/.travis.yml b/.travis.yml index 28728ff..261f125 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,11 @@ python: - "3.4" - "3.5" - "3.6" +matrix: + include: + - python: 3.7 # https://github.com/travis-ci/travis-ci/issues/9069#issuecomment-425720905 + dist: xenial + sudo: true install: - pip install -U pip setuptools - pip install tox-travis pre-commit diff --git a/README.rst b/README.rst index 1e35909..89e3af5 100644 --- a/README.rst +++ b/README.rst @@ -125,8 +125,6 @@ select additional or substitute Maintainers, per `consensus agreements **Current Maintainers of this TC Open Repository** -- `Greg Back `__; GitHub ID: - https://github.com/gtback/; WWW: `MITRE Corporation `__ - `Chris Lenk `__; GitHub ID: https://github.com/clenk/; WWW: `MITRE Corporation `__ diff --git a/docs/guide/creating.ipynb b/docs/guide/creating.ipynb index 61bbe15..058aae3 100644 --- a/docs/guide/creating.ipynb +++ b/docs/guide/creating.ipynb @@ -881,7 +881,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.3" + "version": "3.6.5" } }, "nbformat": 4, diff --git a/docs/guide/patterns.ipynb b/docs/guide/patterns.ipynb new file mode 100644 index 0000000..ee06675 --- /dev/null +++ b/docs/guide/patterns.ipynb @@ -0,0 +1,509 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# STIX2 Patterns\n", + "\n", + "The Python ``stix2`` library supports STIX 2 patterning insofar that patterns may be used for the pattern property of Indicators, identical to the STIX 2 specification. ``stix2`` does not evaluate patterns against STIX 2 content; for that functionality see [cti-pattern-matcher](https://github.com/oasis-open/cti-pattern-matcher).\n", + "\n", + "Patterns in the ``stix2`` library are built compositely from the bottom up, creating subcomponent expressions first before those at higher levels.\n", + "\n", + "## API Tips\n", + "\n", + "### ObservationExpression\n", + "\n", + "Within the STIX 2 Patterning specification, Observation Expressions denote a complete expression to be evaluated against a discrete observation. In other words, an Observation Expression must be created to apply to a single Observation instance. This is further made clear by the visual brackets(```[]```) that encapsulate an Observation Expression. Thus, whatever sub expressions that are within the Observation Expression are meant to be matched against the same Observable instance.\n", + "\n", + "This requirement manifests itself within the ``stix2`` library via ```ObservationExpression```. When creating STIX 2 observation expressions, whenever the current expression is complete, wrap it with ```ObservationExpression()```. This allows the complete pattern expression - no matter its complexity - to be rendered as a proper specification-adhering string. __*Note: When pattern expressions are added to Indicator objects, the expression objects are implicitly converted to string representations*__. While the extra step may seem tedious in the construction of simple pattern expressions, this explicit marking of observation expressions becomes vital when converting the pattern expressions to strings. \n", + "\n", + "In all the examples, you can observe how in the process of building pattern expressions, when an Observation Expression is completed, it is wrapped with ```ObservationExpression()```.\n", + "\n", + "### ParentheticalExpression\n", + "\n", + "Do not be confused by the ```ParentheticalExpression``` object. It is not a distinct expression type but is also used to properly craft pattern expressions by denoting order priority and grouping of expression components. Use it in a similar manner as ```ObservationExpression```, wrapping completed subcomponent expressions with ```ParentheticalExpression()``` if explicit ordering is required. For usage examples with ```ParentheticalExpression```'s, see [here](#Compound-Observation-Expressions).\n", + "\n", + "### BooleanExpressions vs CompoundObservationExpressions\n", + "\n", + "Be careful to note the difference between these two very similar pattern components. \n", + "\n", + "__BooleanExpressions__\n", + "\n", + " - [AndBooleanExpression](../api/stix2.patterns.rst#stix2.patterns.AndBooleanExpression)\n", + " - [OrbooleanExpression](../api/stix2.patterns.rst#stix2.patterns.OrBooleanExpression)\n", + " \n", + " __Usage__: When the boolean sub-expressions refer to the *same* root object \n", + "\n", + " __Example__:\n", + " ```[domain-name:value = \"www.5z8.info\" AND domain-name:resolvess_to_refs[*].value = \"'198.51.100.1/32'\"]```\n", + " \n", + " __Rendering__: when pattern is rendered, brackets or parenthesis will encapsulate boolean expression\n", + " \n", + "__CompoundObservationExpressions__\n", + "\n", + " - [AndObservationExpression](../api/stix2.patterns.rst#stix2.patterns.AndObservationExpression)\n", + " - [OrObservationExpression](../api/stix2.patterns.rst#stix2.patterns.OrObservationExpression)\n", + " \n", + " __Usage__: When the boolean sub-expressions refer to *different* root objects\n", + "\n", + " __Example__:\n", + " ```[file:name=\"foo.dll\"] AND [process:name = \"procfoo\"]```\n", + " \n", + " __Rendering__: when pattern is rendered, brackets will encapsulate each boolean sub-expression\n", + "\n", + "\n", + "\n", + "## Examples\n", + "\n", + "### Comparison Expressions" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "from stix2 import DomainName, File, IPv4Address\n", + "from stix2 import (ObjectPath, EqualityComparisonExpression, ObservationExpression,\n", + " GreaterThanComparisonExpression, IsSubsetComparisonExpression,\n", + " FloatConstant, StringConstant)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Equality Comparison expressions" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\t[domain-name:value = 'site.of.interest.zaz']\n", + "\n", + "\t[file:parent_directory_ref.path = 'C:\\\\Windows\\\\System32']\n", + "\n" + ] + } + ], + "source": [ + "lhs = ObjectPath(\"domain-name\", [\"value\"])\n", + "ece_1 = ObservationExpression(EqualityComparisonExpression(lhs, \"site.of.interest.zaz\"))\n", + "print(\"\\t{}\\n\".format(ece_1))\n", + "\n", + "lhs = ObjectPath(\"file\", [\"parent_directory_ref\",\"path\"])\n", + "ece_2 = ObservationExpression(EqualityComparisonExpression(lhs, \"C:\\\\Windows\\\\System32\"))\n", + "print(\"\\t{}\\n\".format(ece_2))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Greater-than Comparison expressions" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\t[file:extensions.windows-pebinary-ext.sections[*].entropy > 7.0]\n", + "\n" + ] + } + ], + "source": [ + "lhs = ObjectPath(\"file\", [\"extensions\", \"windows-pebinary-ext\", \"sections[*]\", \"entropy\"])\n", + "gte = ObservationExpression(GreaterThanComparisonExpression(lhs, FloatConstant(\"7.0\")))\n", + "print(\"\\t{}\\n\".format(gte))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### IsSubset Comparison expressions" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\t[network-traffic:dst_ref.value ISSUBSET '2001:0db8:dead:beef:0000:0000:0000:0000/64']\n", + "\n" + ] + } + ], + "source": [ + "lhs = ObjectPath(\"network-traffic\", [\"dst_ref\", \"value\"])\n", + "iss = ObservationExpression(IsSubsetComparisonExpression(lhs, StringConstant(\"2001:0db8:dead:beef:0000:0000:0000:0000/64\")))\n", + "print(\"\\t{}\\n\".format(iss))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Compound Observation Expressions" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from stix2 import (IntegerConstant, HashConstant, ObjectPath,\n", + " EqualityComparisonExpression, AndBooleanExpression,\n", + " OrBooleanExpression, ParentheticalExpression,\n", + " AndObservationExpression, OrObservationExpression,\n", + " FollowedByObservationExpression, ObservationExpression)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### AND boolean" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(AND)\n", + "[email-message:sender_ref.value = 'stark@example.com' AND email-message:subject = 'Conference Info']\n", + "\n" + ] + } + ], + "source": [ + "ece3 = EqualityComparisonExpression(ObjectPath(\"email-message\", [\"sender_ref\", \"value\"]), \"stark@example.com\")\n", + "ece4 = EqualityComparisonExpression(ObjectPath(\"email-message\", [\"subject\"]), \"Conference Info\")\n", + "abe = ObservationExpression(AndBooleanExpression([ece3, ece4]))\n", + "print(\"(AND)\\n{}\\n\".format(abe))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### OR boolean" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(OR)\n", + "[url:value = 'http://example.com/foo' OR url:value = 'http://example.com/bar']\n", + "\n" + ] + } + ], + "source": [ + "ece5 = EqualityComparisonExpression(ObjectPath(\"url\", [\"value\"]), \"http://example.com/foo\")\n", + "ece6 = EqualityComparisonExpression(ObjectPath(\"url\", [\"value\"]), \"http://example.com/bar\")\n", + "obe = ObservationExpression(OrBooleanExpression([ece5, ece6]))\n", + "print(\"(OR)\\n{}\\n\".format(obe))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### ( OR ) AND boolean" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(OR,AND)\n", + "[(file:name = 'pdf.exe' OR file:size = 371712) AND file:created = 2014-01-13 07:03:17+00:00]\n", + "\n" + ] + } + ], + "source": [ + "ece7 = EqualityComparisonExpression(ObjectPath(\"file\", [\"name\"]), \"pdf.exe\")\n", + "ece8 = EqualityComparisonExpression(ObjectPath(\"file\", [\"size\"]), IntegerConstant(\"371712\"))\n", + "ece9 = EqualityComparisonExpression(ObjectPath(\"file\", [\"created\"]), \"2014-01-13T07:03:17Z\")\n", + "obe1 = OrBooleanExpression([ece7, ece8])\n", + "pobe = ParentheticalExpression(obe1)\n", + "abe1 = ObservationExpression(AndBooleanExpression([pobe, ece9]))\n", + "print(\"(OR,AND)\\n{}\\n\".format(abe1))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### ( AND ) OR ( OR ) observation" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(AND,OR,OR)\n", + "([file:name = 'foo.dll'] AND [win-registry-key:key = 'HKEY_LOCAL_MACHINE\\\\foo\\\\bar']) OR [process:name = 'fooproc' OR process:name = 'procfoo']\n", + "\n" + ] + } + ], + "source": [ + "ece20 = ObservationExpression(EqualityComparisonExpression(ObjectPath(\"file\", [\"name\"]), \"foo.dll\"))\n", + "ece21 = ObservationExpression(EqualityComparisonExpression(ObjectPath(\"win-registry-key\", [\"key\"]), \"HKEY_LOCAL_MACHINE\\\\foo\\\\bar\"))\n", + "ece22 = EqualityComparisonExpression(ObjectPath(\"process\", [\"name\"]), \"fooproc\")\n", + "ece23 = EqualityComparisonExpression(ObjectPath(\"process\", [\"name\"]), \"procfoo\")\n", + "# NOTE: we need to use AND/OR observation expression instead of just boolean \n", + "# expressions as the operands are not on the same object-type\n", + "aoe = ParentheticalExpression(AndObservationExpression([ece20, ece21]))\n", + "obe2 = ObservationExpression(OrBooleanExpression([ece22, ece23]))\n", + "ooe = OrObservationExpression([aoe, obe2])\n", + "print(\"(AND,OR,OR)\\n{}\\n\".format(ooe))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### FOLLOWED-BY" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(FollowedBy)\n", + "[file:hashes.MD5 = '79054025255fb1a26e4bc422aef54eb4'] FOLLOWEDBY [win-registry-key:key = 'HKEY_LOCAL_MACHINE\\\\foo\\\\bar']\n", + "\n" + ] + } + ], + "source": [ + "ece10 = ObservationExpression(EqualityComparisonExpression(ObjectPath(\"file\", [\"hashes\", \"MD5\"]), HashConstant(\"79054025255fb1a26e4bc422aef54eb4\", \"MD5\")))\n", + "ece11 = ObservationExpression(EqualityComparisonExpression(ObjectPath(\"win-registry-key\", [\"key\"]), \"HKEY_LOCAL_MACHINE\\\\foo\\\\bar\"))\n", + "fbe = FollowedByObservationExpression([ece10, ece11])\n", + "print(\"(FollowedBy)\\n{}\\n\".format(fbe))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Qualified Observation Expressions" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "from stix2 import (TimestampConstant, HashConstant, ObjectPath, EqualityComparisonExpression,\n", + " AndBooleanExpression, WithinQualifier, RepeatQualifier, StartStopQualifier,\n", + " QualifiedObservationExpression, FollowedByObservationExpression,\n", + " ParentheticalExpression, ObservationExpression)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### WITHIN" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(WITHIN)\n", + "([file:hashes.MD5 = '79054025255fb1a26e4bc422aef54eb4'] FOLLOWEDBY [win-registry-key:key = 'HKEY_LOCAL_MACHINE\\\\foo\\\\bar']) WITHIN 300 SECONDS\n", + "\n" + ] + } + ], + "source": [ + "ece10 = ObservationExpression(EqualityComparisonExpression(ObjectPath(\"file\", [\"hashes\", \"MD5\"]), HashConstant(\"79054025255fb1a26e4bc422aef54eb4\", \"MD5\")))\n", + "ece11 = ObservationExpression(EqualityComparisonExpression(ObjectPath(\"win-registry-key\", [\"key\"]), \"HKEY_LOCAL_MACHINE\\\\foo\\\\bar\"))\n", + "fbe = FollowedByObservationExpression([ece10, ece11])\n", + "par = ParentheticalExpression(fbe)\n", + "qoe = QualifiedObservationExpression(par, WithinQualifier(300))\n", + "print(\"(WITHIN)\\n{}\\n\".format(qoe))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### REPEATS, WITHIN" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(REPEAT, WITHIN)\n", + "[network-traffic:dst_ref.type = 'domain-name' AND network-traffic:dst_ref.value = 'example.com'] REPEATS 5 TIMES WITHIN 180 SECONDS\n", + "\n" + ] + } + ], + "source": [ + "ece12 = EqualityComparisonExpression(ObjectPath(\"network-traffic\", [\"dst_ref\", \"type\"]), \"domain-name\")\n", + "ece13 = EqualityComparisonExpression(ObjectPath(\"network-traffic\", [\"dst_ref\", \"value\"]), \"example.com\")\n", + "abe2 = ObservationExpression(AndBooleanExpression([ece12, ece13]))\n", + "qoe1 = QualifiedObservationExpression(QualifiedObservationExpression(abe2, RepeatQualifier(5)), WithinQualifier(180))\n", + "print(\"(REPEAT, WITHIN)\\n{}\\n\".format(qoe1))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### START, STOP" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(START-STOP)\n", + "[file:name = 'foo.dll'] START t'2016-06-01T00:00:00Z' STOP t'2016-07-01T00:00:00Z'\n", + "\n" + ] + } + ], + "source": [ + "ece14 = ObservationExpression(EqualityComparisonExpression(ObjectPath(\"file\", [\"name\"]), \"foo.dll\"))\n", + "ssq = StartStopQualifier(TimestampConstant('2016-06-01T00:00:00Z'), TimestampConstant('2016-07-01T00:00:00Z'))\n", + "qoe2 = QualifiedObservationExpression(ece14, ssq)\n", + "print(\"(START-STOP)\\n{}\\n\".format(qoe2))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Attaching patterns to STIX2 Domain objects\n", + "\n", + "\n", + "### Example" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\n", + " \"type\": \"indicator\",\n", + " \"id\": \"indicator--219bc5fc-fdbf-4b54-a2fc-921be7ab3acb\",\n", + " \"created\": \"2018-08-29T23:58:00.548Z\",\n", + " \"modified\": \"2018-08-29T23:58:00.548Z\",\n", + " \"name\": \"Cryptotorch\",\n", + " \"pattern\": \"[file:name = '$$t00rzch$$.elf']\",\n", + " \"valid_from\": \"2018-08-29T23:58:00.548391Z\",\n", + " \"labels\": [\n", + " \"malware\",\n", + " \"ransomware\"\n", + " ]\n", + "}\n" + ] + } + ], + "source": [ + "from stix2 import Indicator, EqualityComparisonExpression, ObservationExpression\n", + "\n", + "ece14 = ObservationExpression(EqualityComparisonExpression(ObjectPath(\"file\", [\"name\"]), \"$$t00rzch$$.elf\"))\n", + "ind = Indicator(name=\"Cryptotorch\", labels=[\"malware\", \"ransomware\"], pattern=ece14)\n", + "print(ind)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.3" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/setup.py b/setup.py index 1acf1f5..87ff9cd 100644 --- a/setup.py +++ b/setup.py @@ -43,6 +43,7 @@ setup( 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', ], keywords='stix stix2 json cti cyber threat intelligence', packages=find_packages(exclude=['*.test']), diff --git a/stix2/base.py b/stix2/base.py index fe4c5bd..db17a62 100644 --- a/stix2/base.py +++ b/stix2/base.py @@ -334,3 +334,8 @@ class _Extension(_STIXBase): def _check_object_constraints(self): super(_Extension, self)._check_object_constraints() self._check_at_least_one_property() + + +def _cls_init(cls, obj, kwargs): + if getattr(cls, '__init__', object.__init__) is not object.__init__: + cls.__init__(obj, **kwargs) diff --git a/stix2/patterns.py b/stix2/patterns.py index 9668c2a..8bcaea3 100644 --- a/stix2/patterns.py +++ b/stix2/patterns.py @@ -18,6 +18,11 @@ class _Constant(object): class StringConstant(_Constant): + """Pattern string constant + + Args: + value (str): string value + """ def __init__(self, value): self.value = value @@ -26,17 +31,27 @@ class StringConstant(_Constant): class TimestampConstant(_Constant): + """Pattern timestamp constant + + Args: + value (datetime.datetime OR str): if string, must be a timestamp string + """ def __init__(self, value): try: self.value = parse_into_datetime(value) except Exception: - raise ValueError("must be a datetime object or timestamp string.") + raise ValueError("Must be a datetime object or timestamp string.") def __str__(self): return "t%s" % repr(self.value) class IntegerConstant(_Constant): + """Pattern interger constant + + Args: + value (int): integer value + """ def __init__(self, value): try: self.value = int(value) @@ -59,6 +74,13 @@ class FloatConstant(_Constant): class BooleanConstant(_Constant): + """Pattern boolean constant + + Args: + value (str OR int): + (str) 'true', 't' for True; 'false', 'f' for False + (int) 1 for True; 0 for False + """ def __init__(self, value): if isinstance(value, bool): self.value = value @@ -106,6 +128,15 @@ _HASH_REGEX = { class HashConstant(StringConstant): + """Pattern hash constant + + Args: + value (str): hash value + type (str): hash algorithm name. Supported hash algorithms: + "MD5", "MD6", RIPEMD160", "SHA1", "SHA224", "SHA256", + "SHA384", "SHA512", "SHA3224", "SHA3256", "SHA3384", + "SHA3512", "SSDEEP", "WHIRLPOOL" + """ def __init__(self, value, type): key = type.upper().replace('-', '') if key in _HASH_REGEX: @@ -116,7 +147,11 @@ class HashConstant(StringConstant): class BinaryConstant(_Constant): + """Pattern binary constant + Args: + value (str): base64 encoded string value + """ def __init__(self, value): try: base64.b64decode(value) @@ -129,6 +164,11 @@ class BinaryConstant(_Constant): class HexConstant(_Constant): + """Pattern hexadecimal constant + + Args: + value (str): hexadecimal value + """ def __init__(self, value): if not re.match('^([a-fA-F0-9]{2})+$', value): raise ValueError("must contain an even number of hexadecimal characters") @@ -139,6 +179,11 @@ class HexConstant(_Constant): class ListConstant(_Constant): + """Pattern list constant + + Args: + value (list): list of values + """ def __init__(self, values): self.value = values @@ -147,6 +192,12 @@ class ListConstant(_Constant): def make_constant(value): + """Convert value to Pattern constant, best effort attempt + at determining root value type and corresponding conversion + + Args: + value: value to convert to Pattern constant + """ if isinstance(value, _Constant): return value @@ -182,6 +233,16 @@ class _ObjectPathComponent(object): class BasicObjectPathComponent(_ObjectPathComponent): + """Basic object path component (for an observation or expression) + + By "Basic", implies that the object path component is not a + list, object reference or futher referenced property, i.e. terminal + component + + Args: + property_name (str): object property name + is_key (bool): is dictionary key, default: False + """ def __init__(self, property_name, is_key=False): self.property_name = property_name # TODO: set is_key to True if this component is a dictionary key @@ -192,6 +253,12 @@ class BasicObjectPathComponent(_ObjectPathComponent): class ListObjectPathComponent(_ObjectPathComponent): + """List object path component (for an observation or expression) + + Args: + property_name (str): list object property name + index (int): index of the list property's value that is specified + """ def __init__(self, property_name, index): self.property_name = property_name self.index = index @@ -201,6 +268,11 @@ class ListObjectPathComponent(_ObjectPathComponent): class ReferenceObjectPathComponent(_ObjectPathComponent): + """Reference object path component (for an observation or expression) + + Args: + reference_property_name (str): reference object property name + """ def __init__(self, reference_property_name): self.property_name = reference_property_name @@ -209,6 +281,12 @@ class ReferenceObjectPathComponent(_ObjectPathComponent): class ObjectPath(object): + """Pattern operand object (property) path + + Args: + object_type_name (str): name of object type for corresponding object path component + property_path (_ObjectPathComponent OR str): object path + """ def __init__(self, object_type_name, property_path): self.object_type_name = object_type_name self.property_path = [ @@ -221,11 +299,17 @@ class ObjectPath(object): return "%s:%s" % (self.object_type_name, ".".join(["%s" % x for x in self.property_path])) def merge(self, other): + """Extend the object property with that of the supplied object property path""" self.property_path.extend(other.property_path) return self @staticmethod def make_object_path(lhs): + """Create ObjectPath from string encoded object path + + Args: + lhs (str): object path of left-hand-side component of expression + """ path_as_parts = lhs.split(":") return ObjectPath(path_as_parts[0], path_as_parts[1].split(".")) @@ -235,6 +319,14 @@ class _PatternExpression(object): class _ComparisonExpression(_PatternExpression): + """Pattern Comparison Expression + + Args: + operator (str): operator of comparison expression + lhs (ObjectPath OR str): object path of left-hand-side component of expression + rhs (ObjectPath OR str): object path of right-hand-side component of expression + negated (bool): comparison expression negated. Default: False + """ def __init__(self, operator, lhs, rhs, negated=False): if operator == "=" and isinstance(rhs, (ListConstant, list)): self.operator = "IN" @@ -259,56 +351,134 @@ class _ComparisonExpression(_PatternExpression): class EqualityComparisonExpression(_ComparisonExpression): + """Pattern Equality Comparison Expression + + Args: + lhs (ObjectPath OR str): object path of left-hand-side component of expression + rhs (ObjectPath OR str): object path of right-hand-side component of expression + negated (bool): comparison expression negated. Default: False + """ def __init__(self, lhs, rhs, negated=False): super(EqualityComparisonExpression, self).__init__("=", lhs, rhs, negated) class GreaterThanComparisonExpression(_ComparisonExpression): + """Pattern Greater-than Comparison Expression + + Args: + lhs (ObjectPath OR str): object path of left-hand-side component of expression + rhs (ObjectPath OR str): object path of right-hand-side component of expression + negated (bool): comparison expression negated. Default: False + """ def __init__(self, lhs, rhs, negated=False): super(GreaterThanComparisonExpression, self).__init__(">", lhs, rhs, negated) class LessThanComparisonExpression(_ComparisonExpression): + """Pattern Less-than Comparison Expression + + Args: + lhs (ObjectPath OR str): object path of left-hand-side component of expression + rhs (ObjectPath OR str): object path of right-hand-side component of expression + negated (bool): comparison expression negated. Default: False + """ def __init__(self, lhs, rhs, negated=False): super(LessThanComparisonExpression, self).__init__("<", lhs, rhs, negated) class GreaterThanEqualComparisonExpression(_ComparisonExpression): + """Pattern Greater-Than-or-Equal-to Comparison Expression + + Args: + lhs (ObjectPath OR str): object path of left-hand-side component of expression + rhs (ObjectPath OR str): object path of right-hand-side component of expression + negated (bool): comparison expression negated. Default: False + """ def __init__(self, lhs, rhs, negated=False): super(GreaterThanEqualComparisonExpression, self).__init__(">=", lhs, rhs, negated) class LessThanEqualComparisonExpression(_ComparisonExpression): + """Pattern Less-Than-or-Equal-to Comparison Expression + + Args: + lhs (ObjectPath OR str): object path of left-hand-side component of expression + rhs (ObjectPath OR str): object path of right-hand-side component of expression + negated (bool): comparison expression negated. Default: False + """ + def __init__(self, lhs, rhs, negated=False): super(LessThanEqualComparisonExpression, self).__init__("<=", lhs, rhs, negated) class InComparisonExpression(_ComparisonExpression): + """'in' Comparison Expression + + Args: + lhs (ObjectPath OR str): object path of left-hand-side component of expression + rhs (ObjectPath OR str): object path of right-hand-side component of expression + negated (bool): comparison expression negated. Default: False + """ def __init__(self, lhs, rhs, negated=False): super(InComparisonExpression, self).__init__("IN", lhs, rhs, negated) class LikeComparisonExpression(_ComparisonExpression): + """'like' Comparison Expression + + Args: + lhs (ObjectPath OR str): object path of left-hand-side component of expression + rhs (ObjectPath OR str): object path of right-hand-side component of expression + negated (bool): comparison expression negated. Default: False + """ + def __init__(self, lhs, rhs, negated=False): super(LikeComparisonExpression, self).__init__("LIKE", lhs, rhs, negated) class MatchesComparisonExpression(_ComparisonExpression): + """'Matches' Comparison Expression + + Args: + lhs (ObjectPath OR str): object path of left-hand-side component of expression + rhs (ObjectPath OR str): object path of right-hand-side component of expression + negated (bool): comparison expression negated. Default: False + """ def __init__(self, lhs, rhs, negated=False): super(MatchesComparisonExpression, self).__init__("MATCHES", lhs, rhs, negated) class IsSubsetComparisonExpression(_ComparisonExpression): - def __init__(self, lhs, rhs, negated=False): - super(IsSubsetComparisonExpression, self).__init__("ISSUBSET", lhs, rhs, negated) + """ 'is subset' Comparison Expression + + Args: + lhs (ObjectPath OR str): object path of left-hand-side component of expression + rhs (ObjectPath OR str): object path of right-hand-side component of expression + negated (bool): comparison expression negated. Default: False + """ + def __init__(self, lhs, rhs, negated=False): + super(IsSubsetComparisonExpression, self).__init__("ISSUBSET", lhs, rhs, negated) class IsSupersetComparisonExpression(_ComparisonExpression): - def __init__(self, lhs, rhs, negated=False): - super(IsSupersetComparisonExpression, self).__init__("ISSUPERSET", lhs, rhs, negated) + """ 'is super set' Comparison Expression + + Args: + lhs (ObjectPath OR str): object path of left-hand-side component of expression + rhs (ObjectPath OR str): object path of right-hand-side component of expression + negated (bool): comparison expression negated. Default: False + """ + def __init__(self, lhs, rhs, negated=False): + super(IsSupersetComparisonExpression, self).__init__("ISSUPERSET", lhs, rhs, negated) class _BooleanExpression(_PatternExpression): + """Boolean Pattern Expression + + Args: + operator (str): boolean operator + operands (list): boolean operands + """ def __init__(self, operator, operands): self.operator = operator self.operands = [] @@ -324,21 +494,37 @@ class _BooleanExpression(_PatternExpression): def __str__(self): sub_exprs = [] for o in self.operands: - sub_exprs.append("%s" % o) + sub_exprs.append(str(o)) return (" " + self.operator + " ").join(sub_exprs) class AndBooleanExpression(_BooleanExpression): + """'AND' Boolean Pattern Expression. Only use if both operands are of + the same root object. + + Args: + operands (list): AND operands + """ def __init__(self, operands): super(AndBooleanExpression, self).__init__("AND", operands) class OrBooleanExpression(_BooleanExpression): + """'OR' Boolean Pattern Expression. Only use if both operands are of the same root object + + Args: + operands (list): OR operands + """ def __init__(self, operands): super(OrBooleanExpression, self).__init__("OR", operands) class ObservationExpression(_PatternExpression): + """Observation Expression + + Args: + operand (str): observation expression operand + """ def __init__(self, operand): self.operand = operand @@ -347,6 +533,12 @@ class ObservationExpression(_PatternExpression): class _CompoundObservationExpression(_PatternExpression): + """Compound Observation Expression + + Args: + operator (str): compound observation operator + operands (str): compound observation operands + """ def __init__(self, operator, operands): self.operator = operator self.operands = operands @@ -359,21 +551,41 @@ class _CompoundObservationExpression(_PatternExpression): class AndObservationExpression(_CompoundObservationExpression): + """'AND' Compound Observation Pattern Expression + + Args: + operands (str): compound observation operands + """ def __init__(self, operands): super(AndObservationExpression, self).__init__("AND", operands) class OrObservationExpression(_CompoundObservationExpression): + """Pattern 'OR' Compound Observation Expression + + Args: + operands (str): compound observation operands + """ def __init__(self, operands): super(OrObservationExpression, self).__init__("OR", operands) class FollowedByObservationExpression(_CompoundObservationExpression): + """Pattern 'Followed by' Compound Observation Expression + + Args: + operands (str): compound observation operands + """ def __init__(self, operands): super(FollowedByObservationExpression, self).__init__("FOLLOWEDBY", operands) class ParentheticalExpression(_PatternExpression): + """Pattern Parenthetical Observation Expression + + Args: + exp (str): observation expression + """ def __init__(self, exp): self.expression = exp if hasattr(exp, "root_type"): @@ -388,6 +600,11 @@ class _ExpressionQualifier(_PatternExpression): class RepeatQualifier(_ExpressionQualifier): + """Pattern Repeat Qualifier + + Args: + times_to_repeat (int): times the qualifiers is repeated + """ def __init__(self, times_to_repeat): if isinstance(times_to_repeat, IntegerConstant): self.times_to_repeat = times_to_repeat @@ -401,6 +618,11 @@ class RepeatQualifier(_ExpressionQualifier): class WithinQualifier(_ExpressionQualifier): + """Pattern 'Within' Qualifier + + Args: + number_of_seconds (int): seconds value for 'within' qualifier + """ def __init__(self, number_of_seconds): if isinstance(number_of_seconds, IntegerConstant): self.number_of_seconds = number_of_seconds @@ -414,6 +636,12 @@ class WithinQualifier(_ExpressionQualifier): class StartStopQualifier(_ExpressionQualifier): + """Pattern Start/Stop Qualifier + + Args: + start_time (TimestampConstant OR datetime.date): start timestamp for qualifier + stop_time (TimestampConstant OR datetime.date): stop timestamp for qualifier + """ def __init__(self, start_time, stop_time): if isinstance(start_time, TimestampConstant): self.start_time = start_time @@ -433,6 +661,12 @@ class StartStopQualifier(_ExpressionQualifier): class QualifiedObservationExpression(_PatternExpression): + """Pattern Qualified Observation Expression + + Args: + observation_expression (PatternExpression OR _CompoundObservationExpression OR ): pattern expression + qualifier (_ExpressionQualifier): pattern expression qualifier + """ def __init__(self, observation_expression, qualifier): self.observation_expression = observation_expression self.qualifier = qualifier diff --git a/stix2/v20/sdo.py b/stix2/v20/sdo.py index cf4ee50..40c987e 100644 --- a/stix2/v20/sdo.py +++ b/stix2/v20/sdo.py @@ -368,5 +368,4 @@ def CustomObject(type='x-custom-type', properties=None): sorted([x for x in properties if x[0].startswith('x_')], key=lambda x: x[0]), ])) return _custom_object_builder(cls, type, _properties, '2.0') - return wrapper diff --git a/tox.ini b/tox.ini index 48a28de..f3a10fb 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py27,py34,py35,py36,style,isort-check,packaging +envlist = py27,py34,py35,py36,py37,style,isort-check,packaging [testenv] deps = @@ -44,3 +44,4 @@ python = 3.4: py34, style 3.5: py35, style 3.6: py36, style, packaging + 3.7: py37, style