merge in master to add_vistor
commit
d5d65535a3
187
README.rst
187
README.rst
|
@ -3,32 +3,40 @@
|
||||||
cti-python-stix2
|
cti-python-stix2
|
||||||
================
|
================
|
||||||
|
|
||||||
This is an `OASIS TC Open Repository <https://www.oasis-open.org/resources/open-repositories/>`__.
|
This is an `OASIS TC Open
|
||||||
|
Repository <https://www.oasis-open.org/resources/open-
|
||||||
|
repositories/>`__.
|
||||||
See the `Governance <#governance>`__ section for more information.
|
See the `Governance <#governance>`__ section for more information.
|
||||||
|
|
||||||
This repository provides Python APIs for serializing and de-serializing STIX2
|
This repository provides Python APIs for serializing and de-
|
||||||
JSON content, along with higher-level APIs for common tasks, including data
|
serializing
|
||||||
markings, versioning, and for resolving STIX IDs across multiple data sources.
|
STIX 2 JSON content, along with higher-level APIs for common tasks,
|
||||||
|
including data markings, versioning, and for resolving STIX IDs across
|
||||||
|
multiple data sources.
|
||||||
|
|
||||||
For more information, see `the documentation <https://stix2.readthedocs.io/>`__ on ReadTheDocs.
|
For more information, see `the
|
||||||
|
documentation <https://stix2.readthedocs.io/>`__ on
|
||||||
|
ReadTheDocs.
|
||||||
|
|
||||||
Installation
|
Installation
|
||||||
------------
|
------------
|
||||||
|
|
||||||
Install with `pip <https://pip.pypa.io/en/stable/>`__:
|
Install with `pip <https://pip.pypa.io/en/stable/>`__:
|
||||||
|
|
||||||
.. code-block:: bash
|
::
|
||||||
|
|
||||||
$ pip install stix2
|
pip install stix2
|
||||||
|
|
||||||
Usage
|
Usage
|
||||||
-----
|
-----
|
||||||
|
|
||||||
To create a STIX object, provide keyword arguments to the type's constructor.
|
To create a STIX object, provide keyword arguments to the type's
|
||||||
Certain required attributes of all objects, such as ``type`` or ``id``, will
|
constructor. Certain required attributes of all objects, such as
|
||||||
be set automatically if not provided as keyword arguments.
|
``type`` or
|
||||||
|
``id``, will be set automatically if not provided as keyword
|
||||||
|
arguments.
|
||||||
|
|
||||||
.. code-block:: python
|
.. code:: python
|
||||||
|
|
||||||
from stix2 import Indicator
|
from stix2 import Indicator
|
||||||
|
|
||||||
|
@ -36,9 +44,10 @@ be set automatically if not provided as keyword arguments.
|
||||||
indicator_types=["malicious-activity"],
|
indicator_types=["malicious-activity"],
|
||||||
pattern="[file:hashes.md5 = 'd41d8cd98f00b204e9800998ecf8427e']")
|
pattern="[file:hashes.md5 = 'd41d8cd98f00b204e9800998ecf8427e']")
|
||||||
|
|
||||||
To parse a STIX JSON string into a Python STIX object, use ``parse()``:
|
To parse a STIX JSON string into a Python STIX object, use
|
||||||
|
``parse()``:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code:: python
|
||||||
|
|
||||||
from stix2 import parse
|
from stix2 import parse
|
||||||
|
|
||||||
|
@ -58,95 +67,145 @@ To parse a STIX JSON string into a Python STIX object, use ``parse()``:
|
||||||
|
|
||||||
print(indicator)
|
print(indicator)
|
||||||
|
|
||||||
For more in-depth documentation, please see `https://stix2.readthedocs.io/ <https://stix2.readthedocs.io/>`__.
|
For more in-depth documentation, please see
|
||||||
|
`https://stix2.readthedocs.io/ <https://stix2.readthedocs.io/>`__.
|
||||||
|
|
||||||
STIX 2.X Technical Specification Support
|
STIX 2.X Technical Specification Support
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
|
|
||||||
This version of python-stix2 supports STIX 2.1 by default. Although, the
|
This version of python-stix2 supports STIX 2.0 by default. Although,
|
||||||
`stix2` Python library is built to support multiple versions of the STIX
|
the
|
||||||
Technical Specification. With every major release of stix2 the ``import stix2``
|
`stix2` Python library is built to support multiple versions of the
|
||||||
statement will automatically load the SDO/SROs equivalent to the most recent
|
STIX
|
||||||
supported 2.X Technical Specification. Please see the library documentation
|
Technical Specification. With every major release of stix2 the
|
||||||
|
``import stix2``
|
||||||
|
statement will automatically load the SDO/SROs equivalent to the most
|
||||||
|
recent
|
||||||
|
supported 2.X Technical Specification. Please see the library
|
||||||
|
documentation
|
||||||
for more details.
|
for more details.
|
||||||
|
|
||||||
Governance
|
Governance
|
||||||
----------
|
----------
|
||||||
|
|
||||||
This GitHub public repository (**https://github.com/oasis-open/cti-python-stix2**) was
|
This GitHub public repository (
|
||||||
`proposed <https://lists.oasis-open.org/archives/cti/201702/msg00008.html>`__ and
|
**https://github.com/oasis-open/cti-python-stix2** ) was
|
||||||
`approved <https://www.oasis-open.org/committees/download.php/60009/>`__
|
`proposed <https://lists.oasis-
|
||||||
|
open.org/archives/cti/201702/msg00008.html>`__
|
||||||
|
and
|
||||||
|
`approved <https://www.oasis-
|
||||||
|
open.org/committees/download.php/60009/>`__
|
||||||
[`bis <https://issues.oasis-open.org/browse/TCADMIN-2549>`__] by the
|
[`bis <https://issues.oasis-open.org/browse/TCADMIN-2549>`__] by the
|
||||||
`OASIS Cyber Threat Intelligence (CTI) TC <https://www.oasis-open.org/committees/cti/>`__
|
`OASIS Cyber Threat Intelligence (CTI)
|
||||||
as an `OASIS TC Open Repository <https://www.oasis-open.org/resources/open-repositories/>`__
|
TC <https://www.oasis-open.org/committees/cti/>`__ as an `OASIS TC
|
||||||
to support development of open source resources related to Technical Committee work.
|
Open
|
||||||
|
Repository <https://www.oasis-open.org/resources/open-
|
||||||
|
repositories/>`__
|
||||||
|
to support development of open source resources related to Technical
|
||||||
|
Committee work.
|
||||||
|
|
||||||
While this TC Open Repository remains associated with the sponsor TC, its
|
While this TC Open Repository remains associated with the sponsor TC,
|
||||||
development priorities, leadership, intellectual property terms, participation
|
its
|
||||||
rules, and other matters of governance are `separate and distinct
|
development priorities, leadership, intellectual property terms,
|
||||||
<https://github.com/oasis-open/cti-python-stix2/blob/master/CONTRIBUTING.md#governance-distinct-from-oasis-tc-process>`__
|
participation rules, and other matters of governance are `separate and
|
||||||
|
distinct <https://github.com/oasis-open/cti-python-
|
||||||
|
stix2/blob/master/CONTRIBUTING.md#governance-distinct-from-oasis-tc-
|
||||||
|
process>`__
|
||||||
from the OASIS TC Process and related policies.
|
from the OASIS TC Process and related policies.
|
||||||
|
|
||||||
All contributions made to this TC Open Repository are subject to open
|
All contributions made to this TC Open Repository are subject to open
|
||||||
source license terms expressed in the `BSD-3-Clause License <https://www.oasis-open.org/sites/www.oasis-open.org/files/BSD-3-Clause.txt>`__.
|
source license terms expressed in the `BSD-3-Clause
|
||||||
That license was selected as the declared `"Applicable License" <https://www.oasis-open.org/resources/open-repositories/licenses>`__
|
License <https://www.oasis-open.org/sites/www.oasis-
|
||||||
|
open.org/files/BSD-3-Clause.txt>`__.
|
||||||
|
That license was selected as the declared `"Applicable
|
||||||
|
License" <https://www.oasis-open.org/resources/open-
|
||||||
|
repositories/licenses>`__
|
||||||
when the TC Open Repository was created.
|
when the TC Open Repository was created.
|
||||||
|
|
||||||
As documented in `"Public Participation Invited
|
As documented in `"Public Participation
|
||||||
<https://github.com/oasis-open/cti-python-stix2/blob/master/CONTRIBUTING.md#public-participation-invited>`__",
|
Invited <https://github.com/oasis-open/cti-python-
|
||||||
contributions to this OASIS TC Open Repository are invited from all parties,
|
stix2/blob/master/CONTRIBUTING.md#public-participation-invited>`__",
|
||||||
whether affiliated with OASIS or not. Participants must have a GitHub account,
|
contributions to this OASIS TC Open Repository are invited from all
|
||||||
but no fees or OASIS membership obligations are required. Participation is
|
parties, whether affiliated with OASIS or not. Participants must have
|
||||||
expected to be consistent with the `OASIS TC Open Repository Guidelines and Procedures
|
a
|
||||||
<https://www.oasis-open.org/policies-guidelines/open-repositories>`__,
|
GitHub account, but no fees or OASIS membership obligations are
|
||||||
the open source `LICENSE <https://github.com/oasis-open/cti-python-stix2/blob/master/LICENSE>`__
|
required. Participation is expected to be consistent with the `OASIS
|
||||||
|
TC Open Repository Guidelines and
|
||||||
|
Procedures <https://www.oasis-open.org/policies-guidelines/open-
|
||||||
|
repositories>`__,
|
||||||
|
the open source
|
||||||
|
`LICENSE <https://github.com/oasis-open/cti-python-
|
||||||
|
stix2/blob/master/LICENSE>`__
|
||||||
designated for this particular repository, and the requirement for an
|
designated for this particular repository, and the requirement for an
|
||||||
`Individual Contributor License Agreement <https://www.oasis-open.org/resources/open-repositories/cla/individual-cla>`__
|
`Individual Contributor License
|
||||||
|
Agreement <https://www.oasis-open.org/resources/open-
|
||||||
|
repositories/cla/individual-cla>`__
|
||||||
that governs intellectual property.
|
that governs intellectual property.
|
||||||
|
|
||||||
Maintainers
|
Maintainers
|
||||||
~~~~~~~~~~~
|
~~~~~~~~~~~
|
||||||
|
|
||||||
TC Open Repository `Maintainers <https://www.oasis-open.org/resources/open-repositories/maintainers-guide>`__
|
TC Open Repository
|
||||||
|
`Maintainers <https://www.oasis-open.org/resources/open-
|
||||||
|
repositories/maintainers-guide>`__
|
||||||
are responsible for oversight of this project's community development
|
are responsible for oversight of this project's community development
|
||||||
activities, including evaluation of GitHub
|
activities, including evaluation of GitHub `pull
|
||||||
`pull requests <https://github.com/oasis-open/cti-python-stix2/blob/master/CONTRIBUTING.md#fork-and-pull-collaboration-model>`__
|
requests <https://github.com/oasis-open/cti-python-
|
||||||
and `preserving <https://www.oasis-open.org/policies-guidelines/open-repositories#repositoryManagement>`__
|
stix2/blob/master/CONTRIBUTING.md#fork-and-pull-collaboration-
|
||||||
open source principles of openness and fairness. Maintainers are recognized
|
model>`__
|
||||||
and trusted experts who serve to implement community goals and consensus design
|
and
|
||||||
preferences.
|
`preserving <https://www.oasis-open.org/policies-guidelines/open-
|
||||||
|
repositories#repositoryManagement>`__
|
||||||
|
open source principles of openness and fairness. Maintainers are
|
||||||
|
recognized and trusted experts who serve to implement community goals
|
||||||
|
and consensus design preferences.
|
||||||
|
|
||||||
Initially, the associated TC members have designated one or more persons to
|
Initially, the associated TC members have designated one or more
|
||||||
serve as Maintainer(s); subsequently, participating community members may
|
persons
|
||||||
select additional or substitute Maintainers, per `consensus agreements
|
to serve as Maintainer(s); subsequently, participating community
|
||||||
<https://www.oasis-open.org/resources/open-repositories/maintainers-guide#additionalMaintainers>`__.
|
members
|
||||||
|
may select additional or substitute Maintainers, per `consensus
|
||||||
|
agreements <https://www.oasis-open.org/resources/open-
|
||||||
|
repositories/maintainers-guide#additionalMaintainers>`__.
|
||||||
|
|
||||||
.. _currentmaintainers:
|
.. _currentMaintainers:
|
||||||
|
|
||||||
**Current Maintainers of this TC Open Repository**
|
**Current Maintainers of this TC Open Repository**
|
||||||
|
|
||||||
- `Greg Back <mailto:gback@mitre.org>`__; GitHub ID:
|
|
||||||
https://github.com/gtback/; WWW: `MITRE Corporation <http://www.mitre.org/>`__
|
|
||||||
- `Chris Lenk <mailto:clenk@mitre.org>`__; GitHub ID:
|
- `Chris Lenk <mailto:clenk@mitre.org>`__; GitHub ID:
|
||||||
https://github.com/clenk/; WWW: `MITRE Corporation <http://www.mitre.org/>`__
|
https://github.com/clenk/; WWW: `MITRE
|
||||||
|
Corporation <http://www.mitre.org/>`__
|
||||||
|
|
||||||
About OASIS TC Open Repositories
|
About OASIS TC Open Repositories
|
||||||
--------------------------------
|
--------------------------------
|
||||||
|
|
||||||
- `TC Open Repositories: Overview and Resources <https://www.oasis-open.org/resources/open-repositories/>`__
|
- `TC Open Repositories: Overview and
|
||||||
- `Frequently Asked Questions <https://www.oasis-open.org/resources/open-repositories/faq>`__
|
Resources <https://www.oasis-open.org/resources/open-
|
||||||
- `Open Source Licenses <https://www.oasis-open.org/resources/open-repositories/licenses>`__
|
repositories/>`__
|
||||||
- `Contributor License Agreements (CLAs) <https://www.oasis-open.org/resources/open-repositories/cla>`__
|
- `Frequently Asked
|
||||||
- `Maintainers' Guidelines and Agreement <https://www.oasis-open.org/resources/open-repositories/maintainers-guide>`__
|
Questions <https://www.oasis-open.org/resources/open-
|
||||||
|
repositories/faq>`__
|
||||||
|
- `Open Source
|
||||||
|
Licenses <https://www.oasis-open.org/resources/open-
|
||||||
|
repositories/licenses>`__
|
||||||
|
- `Contributor License Agreements
|
||||||
|
(CLAs) <https://www.oasis-open.org/resources/open-
|
||||||
|
repositories/cla>`__
|
||||||
|
- `Maintainers' Guidelines and
|
||||||
|
Agreement <https://www.oasis-open.org/resources/open-
|
||||||
|
repositories/maintainers-guide>`__
|
||||||
|
|
||||||
Feedback
|
Feedback
|
||||||
--------
|
--------
|
||||||
|
|
||||||
Questions or comments about this TC Open Repository's activities should be
|
Questions or comments about this TC Open Repository's activities
|
||||||
composed as GitHub issues or comments. If use of an issue/comment is not
|
should be
|
||||||
|
composed as GitHub issues or comments. If use of an issue/comment is
|
||||||
|
not
|
||||||
possible or appropriate, questions may be directed by email to the
|
possible or appropriate, questions may be directed by email to the
|
||||||
Maintainer(s) `listed above <#currentmaintainers>`__. Please send general
|
Maintainer(s) `listed above <#currentmaintainers>`__. Please send
|
||||||
questions about TC Open Repository participation to OASIS Staff at
|
general questions about TC Open Repository participation to OASIS
|
||||||
|
Staff at
|
||||||
repository-admin@oasis-open.org and any specific CLA-related questions
|
repository-admin@oasis-open.org and any specific CLA-related questions
|
||||||
to repository-cla@oasis-open.org.
|
to repository-cla@oasis-open.org.
|
||||||
|
|
||||||
|
|
|
@ -881,7 +881,7 @@
|
||||||
"name": "python",
|
"name": "python",
|
||||||
"nbconvert_exporter": "python",
|
"nbconvert_exporter": "python",
|
||||||
"pygments_lexer": "ipython3",
|
"pygments_lexer": "ipython3",
|
||||||
"version": "3.6.3"
|
"version": "3.6.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nbformat": 4,
|
"nbformat": 4,
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -18,6 +18,11 @@ class _Constant(object):
|
||||||
|
|
||||||
|
|
||||||
class StringConstant(_Constant):
|
class StringConstant(_Constant):
|
||||||
|
"""Pattern string constant
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value (str): string value
|
||||||
|
"""
|
||||||
def __init__(self, value):
|
def __init__(self, value):
|
||||||
self.value = value
|
self.value = value
|
||||||
|
|
||||||
|
@ -26,17 +31,27 @@ class StringConstant(_Constant):
|
||||||
|
|
||||||
|
|
||||||
class TimestampConstant(_Constant):
|
class TimestampConstant(_Constant):
|
||||||
|
"""Pattern timestamp constant
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value (datetime.datetime OR str): if string, must be a timestamp string
|
||||||
|
"""
|
||||||
def __init__(self, value):
|
def __init__(self, value):
|
||||||
try:
|
try:
|
||||||
self.value = parse_into_datetime(value)
|
self.value = parse_into_datetime(value)
|
||||||
except Exception:
|
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):
|
def __str__(self):
|
||||||
return "t%s" % repr(self.value)
|
return "t%s" % repr(self.value)
|
||||||
|
|
||||||
|
|
||||||
class IntegerConstant(_Constant):
|
class IntegerConstant(_Constant):
|
||||||
|
"""Pattern interger constant
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value (int): integer value
|
||||||
|
"""
|
||||||
def __init__(self, value):
|
def __init__(self, value):
|
||||||
try:
|
try:
|
||||||
self.value = int(value)
|
self.value = int(value)
|
||||||
|
@ -59,6 +74,13 @@ class FloatConstant(_Constant):
|
||||||
|
|
||||||
|
|
||||||
class BooleanConstant(_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):
|
def __init__(self, value):
|
||||||
if isinstance(value, bool):
|
if isinstance(value, bool):
|
||||||
self.value = value
|
self.value = value
|
||||||
|
@ -106,6 +128,15 @@ _HASH_REGEX = {
|
||||||
|
|
||||||
|
|
||||||
class HashConstant(StringConstant):
|
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):
|
def __init__(self, value, type):
|
||||||
key = type.upper().replace('-', '')
|
key = type.upper().replace('-', '')
|
||||||
if key in _HASH_REGEX:
|
if key in _HASH_REGEX:
|
||||||
|
@ -116,7 +147,11 @@ class HashConstant(StringConstant):
|
||||||
|
|
||||||
|
|
||||||
class BinaryConstant(_Constant):
|
class BinaryConstant(_Constant):
|
||||||
|
"""Pattern binary constant
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value (str): base64 encoded string value
|
||||||
|
"""
|
||||||
def __init__(self, value):
|
def __init__(self, value):
|
||||||
try:
|
try:
|
||||||
base64.b64decode(value)
|
base64.b64decode(value)
|
||||||
|
@ -129,6 +164,11 @@ class BinaryConstant(_Constant):
|
||||||
|
|
||||||
|
|
||||||
class HexConstant(_Constant):
|
class HexConstant(_Constant):
|
||||||
|
"""Pattern hexadecimal constant
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value (str): hexadecimal value
|
||||||
|
"""
|
||||||
def __init__(self, value):
|
def __init__(self, value):
|
||||||
if not re.match('^([a-fA-F0-9]{2})+$', value):
|
if not re.match('^([a-fA-F0-9]{2})+$', value):
|
||||||
raise ValueError("must contain an even number of hexadecimal characters")
|
raise ValueError("must contain an even number of hexadecimal characters")
|
||||||
|
@ -139,6 +179,11 @@ class HexConstant(_Constant):
|
||||||
|
|
||||||
|
|
||||||
class ListConstant(_Constant):
|
class ListConstant(_Constant):
|
||||||
|
"""Pattern list constant
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value (list): list of values
|
||||||
|
"""
|
||||||
def __init__(self, values):
|
def __init__(self, values):
|
||||||
self.value = values
|
self.value = values
|
||||||
|
|
||||||
|
@ -147,6 +192,12 @@ class ListConstant(_Constant):
|
||||||
|
|
||||||
|
|
||||||
def make_constant(value):
|
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):
|
if isinstance(value, _Constant):
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
@ -182,6 +233,16 @@ class _ObjectPathComponent(object):
|
||||||
|
|
||||||
|
|
||||||
class BasicObjectPathComponent(_ObjectPathComponent):
|
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):
|
def __init__(self, property_name, is_key=False):
|
||||||
self.property_name = property_name
|
self.property_name = property_name
|
||||||
# TODO: set is_key to True if this component is a dictionary key
|
# TODO: set is_key to True if this component is a dictionary key
|
||||||
|
@ -192,6 +253,12 @@ class BasicObjectPathComponent(_ObjectPathComponent):
|
||||||
|
|
||||||
|
|
||||||
class ListObjectPathComponent(_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):
|
def __init__(self, property_name, index):
|
||||||
self.property_name = property_name
|
self.property_name = property_name
|
||||||
self.index = index
|
self.index = index
|
||||||
|
@ -201,6 +268,11 @@ class ListObjectPathComponent(_ObjectPathComponent):
|
||||||
|
|
||||||
|
|
||||||
class ReferenceObjectPathComponent(_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):
|
def __init__(self, reference_property_name):
|
||||||
self.property_name = reference_property_name
|
self.property_name = reference_property_name
|
||||||
|
|
||||||
|
@ -209,6 +281,12 @@ class ReferenceObjectPathComponent(_ObjectPathComponent):
|
||||||
|
|
||||||
|
|
||||||
class ObjectPath(object):
|
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):
|
def __init__(self, object_type_name, property_path):
|
||||||
self.object_type_name = object_type_name
|
self.object_type_name = object_type_name
|
||||||
self.property_path = [
|
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]))
|
return "%s:%s" % (self.object_type_name, ".".join(["%s" % x for x in self.property_path]))
|
||||||
|
|
||||||
def merge(self, other):
|
def merge(self, other):
|
||||||
|
"""Extend the object property with that of the supplied object property path"""
|
||||||
self.property_path.extend(other.property_path)
|
self.property_path.extend(other.property_path)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def make_object_path(lhs):
|
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(":")
|
path_as_parts = lhs.split(":")
|
||||||
return ObjectPath(path_as_parts[0], path_as_parts[1].split("."))
|
return ObjectPath(path_as_parts[0], path_as_parts[1].split("."))
|
||||||
|
|
||||||
|
@ -235,6 +319,14 @@ class _PatternExpression(object):
|
||||||
|
|
||||||
|
|
||||||
class _ComparisonExpression(_PatternExpression):
|
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):
|
def __init__(self, operator, lhs, rhs, negated=False):
|
||||||
if operator == "=" and isinstance(rhs, (ListConstant, list)):
|
if operator == "=" and isinstance(rhs, (ListConstant, list)):
|
||||||
self.operator = "IN"
|
self.operator = "IN"
|
||||||
|
@ -259,56 +351,134 @@ class _ComparisonExpression(_PatternExpression):
|
||||||
|
|
||||||
|
|
||||||
class EqualityComparisonExpression(_ComparisonExpression):
|
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):
|
def __init__(self, lhs, rhs, negated=False):
|
||||||
super(EqualityComparisonExpression, self).__init__("=", lhs, rhs, negated)
|
super(EqualityComparisonExpression, self).__init__("=", lhs, rhs, negated)
|
||||||
|
|
||||||
|
|
||||||
class GreaterThanComparisonExpression(_ComparisonExpression):
|
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):
|
def __init__(self, lhs, rhs, negated=False):
|
||||||
super(GreaterThanComparisonExpression, self).__init__(">", lhs, rhs, negated)
|
super(GreaterThanComparisonExpression, self).__init__(">", lhs, rhs, negated)
|
||||||
|
|
||||||
|
|
||||||
class LessThanComparisonExpression(_ComparisonExpression):
|
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):
|
def __init__(self, lhs, rhs, negated=False):
|
||||||
super(LessThanComparisonExpression, self).__init__("<", lhs, rhs, negated)
|
super(LessThanComparisonExpression, self).__init__("<", lhs, rhs, negated)
|
||||||
|
|
||||||
|
|
||||||
class GreaterThanEqualComparisonExpression(_ComparisonExpression):
|
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):
|
def __init__(self, lhs, rhs, negated=False):
|
||||||
super(GreaterThanEqualComparisonExpression, self).__init__(">=", lhs, rhs, negated)
|
super(GreaterThanEqualComparisonExpression, self).__init__(">=", lhs, rhs, negated)
|
||||||
|
|
||||||
|
|
||||||
class LessThanEqualComparisonExpression(_ComparisonExpression):
|
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):
|
def __init__(self, lhs, rhs, negated=False):
|
||||||
super(LessThanEqualComparisonExpression, self).__init__("<=", lhs, rhs, negated)
|
super(LessThanEqualComparisonExpression, self).__init__("<=", lhs, rhs, negated)
|
||||||
|
|
||||||
|
|
||||||
class InComparisonExpression(_ComparisonExpression):
|
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):
|
def __init__(self, lhs, rhs, negated=False):
|
||||||
super(InComparisonExpression, self).__init__("IN", lhs, rhs, negated)
|
super(InComparisonExpression, self).__init__("IN", lhs, rhs, negated)
|
||||||
|
|
||||||
|
|
||||||
class LikeComparisonExpression(_ComparisonExpression):
|
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):
|
def __init__(self, lhs, rhs, negated=False):
|
||||||
super(LikeComparisonExpression, self).__init__("LIKE", lhs, rhs, negated)
|
super(LikeComparisonExpression, self).__init__("LIKE", lhs, rhs, negated)
|
||||||
|
|
||||||
|
|
||||||
class MatchesComparisonExpression(_ComparisonExpression):
|
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):
|
def __init__(self, lhs, rhs, negated=False):
|
||||||
super(MatchesComparisonExpression, self).__init__("MATCHES", lhs, rhs, negated)
|
super(MatchesComparisonExpression, self).__init__("MATCHES", lhs, rhs, negated)
|
||||||
|
|
||||||
|
|
||||||
class IsSubsetComparisonExpression(_ComparisonExpression):
|
class IsSubsetComparisonExpression(_ComparisonExpression):
|
||||||
|
""" '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):
|
def __init__(self, lhs, rhs, negated=False):
|
||||||
super(IsSubsetComparisonExpression, self).__init__("ISSUBSET", lhs, rhs, negated)
|
super(IsSubsetComparisonExpression, self).__init__("ISSUBSET", lhs, rhs, negated)
|
||||||
|
|
||||||
|
|
||||||
class IsSupersetComparisonExpression(_ComparisonExpression):
|
class IsSupersetComparisonExpression(_ComparisonExpression):
|
||||||
|
""" '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):
|
def __init__(self, lhs, rhs, negated=False):
|
||||||
super(IsSupersetComparisonExpression, self).__init__("ISSUPERSET", lhs, rhs, negated)
|
super(IsSupersetComparisonExpression, self).__init__("ISSUPERSET", lhs, rhs, negated)
|
||||||
|
|
||||||
|
|
||||||
class _BooleanExpression(_PatternExpression):
|
class _BooleanExpression(_PatternExpression):
|
||||||
|
"""Boolean Pattern Expression
|
||||||
|
|
||||||
|
Args:
|
||||||
|
operator (str): boolean operator
|
||||||
|
operands (list): boolean operands
|
||||||
|
"""
|
||||||
def __init__(self, operator, operands):
|
def __init__(self, operator, operands):
|
||||||
self.operator = operator
|
self.operator = operator
|
||||||
self.operands = []
|
self.operands = []
|
||||||
|
@ -324,21 +494,37 @@ class _BooleanExpression(_PatternExpression):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
sub_exprs = []
|
sub_exprs = []
|
||||||
for o in self.operands:
|
for o in self.operands:
|
||||||
sub_exprs.append("%s" % o)
|
sub_exprs.append(str(o))
|
||||||
return (" " + self.operator + " ").join(sub_exprs)
|
return (" " + self.operator + " ").join(sub_exprs)
|
||||||
|
|
||||||
|
|
||||||
class AndBooleanExpression(_BooleanExpression):
|
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):
|
def __init__(self, operands):
|
||||||
super(AndBooleanExpression, self).__init__("AND", operands)
|
super(AndBooleanExpression, self).__init__("AND", operands)
|
||||||
|
|
||||||
|
|
||||||
class OrBooleanExpression(_BooleanExpression):
|
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):
|
def __init__(self, operands):
|
||||||
super(OrBooleanExpression, self).__init__("OR", operands)
|
super(OrBooleanExpression, self).__init__("OR", operands)
|
||||||
|
|
||||||
|
|
||||||
class ObservationExpression(_PatternExpression):
|
class ObservationExpression(_PatternExpression):
|
||||||
|
"""Observation Expression
|
||||||
|
|
||||||
|
Args:
|
||||||
|
operand (str): observation expression operand
|
||||||
|
"""
|
||||||
def __init__(self, operand):
|
def __init__(self, operand):
|
||||||
self.operand = operand
|
self.operand = operand
|
||||||
|
|
||||||
|
@ -347,6 +533,12 @@ class ObservationExpression(_PatternExpression):
|
||||||
|
|
||||||
|
|
||||||
class _CompoundObservationExpression(_PatternExpression):
|
class _CompoundObservationExpression(_PatternExpression):
|
||||||
|
"""Compound Observation Expression
|
||||||
|
|
||||||
|
Args:
|
||||||
|
operator (str): compound observation operator
|
||||||
|
operands (str): compound observation operands
|
||||||
|
"""
|
||||||
def __init__(self, operator, operands):
|
def __init__(self, operator, operands):
|
||||||
self.operator = operator
|
self.operator = operator
|
||||||
self.operands = operands
|
self.operands = operands
|
||||||
|
@ -359,21 +551,41 @@ class _CompoundObservationExpression(_PatternExpression):
|
||||||
|
|
||||||
|
|
||||||
class AndObservationExpression(_CompoundObservationExpression):
|
class AndObservationExpression(_CompoundObservationExpression):
|
||||||
|
"""'AND' Compound Observation Pattern Expression
|
||||||
|
|
||||||
|
Args:
|
||||||
|
operands (str): compound observation operands
|
||||||
|
"""
|
||||||
def __init__(self, operands):
|
def __init__(self, operands):
|
||||||
super(AndObservationExpression, self).__init__("AND", operands)
|
super(AndObservationExpression, self).__init__("AND", operands)
|
||||||
|
|
||||||
|
|
||||||
class OrObservationExpression(_CompoundObservationExpression):
|
class OrObservationExpression(_CompoundObservationExpression):
|
||||||
|
"""Pattern 'OR' Compound Observation Expression
|
||||||
|
|
||||||
|
Args:
|
||||||
|
operands (str): compound observation operands
|
||||||
|
"""
|
||||||
def __init__(self, operands):
|
def __init__(self, operands):
|
||||||
super(OrObservationExpression, self).__init__("OR", operands)
|
super(OrObservationExpression, self).__init__("OR", operands)
|
||||||
|
|
||||||
|
|
||||||
class FollowedByObservationExpression(_CompoundObservationExpression):
|
class FollowedByObservationExpression(_CompoundObservationExpression):
|
||||||
|
"""Pattern 'Followed by' Compound Observation Expression
|
||||||
|
|
||||||
|
Args:
|
||||||
|
operands (str): compound observation operands
|
||||||
|
"""
|
||||||
def __init__(self, operands):
|
def __init__(self, operands):
|
||||||
super(FollowedByObservationExpression, self).__init__("FOLLOWEDBY", operands)
|
super(FollowedByObservationExpression, self).__init__("FOLLOWEDBY", operands)
|
||||||
|
|
||||||
|
|
||||||
class ParentheticalExpression(_PatternExpression):
|
class ParentheticalExpression(_PatternExpression):
|
||||||
|
"""Pattern Parenthetical Observation Expression
|
||||||
|
|
||||||
|
Args:
|
||||||
|
exp (str): observation expression
|
||||||
|
"""
|
||||||
def __init__(self, exp):
|
def __init__(self, exp):
|
||||||
self.expression = exp
|
self.expression = exp
|
||||||
if hasattr(exp, "root_type"):
|
if hasattr(exp, "root_type"):
|
||||||
|
@ -388,6 +600,11 @@ class _ExpressionQualifier(_PatternExpression):
|
||||||
|
|
||||||
|
|
||||||
class RepeatQualifier(_ExpressionQualifier):
|
class RepeatQualifier(_ExpressionQualifier):
|
||||||
|
"""Pattern Repeat Qualifier
|
||||||
|
|
||||||
|
Args:
|
||||||
|
times_to_repeat (int): times the qualifiers is repeated
|
||||||
|
"""
|
||||||
def __init__(self, times_to_repeat):
|
def __init__(self, times_to_repeat):
|
||||||
if isinstance(times_to_repeat, IntegerConstant):
|
if isinstance(times_to_repeat, IntegerConstant):
|
||||||
self.times_to_repeat = times_to_repeat
|
self.times_to_repeat = times_to_repeat
|
||||||
|
@ -401,6 +618,11 @@ class RepeatQualifier(_ExpressionQualifier):
|
||||||
|
|
||||||
|
|
||||||
class WithinQualifier(_ExpressionQualifier):
|
class WithinQualifier(_ExpressionQualifier):
|
||||||
|
"""Pattern 'Within' Qualifier
|
||||||
|
|
||||||
|
Args:
|
||||||
|
number_of_seconds (int): seconds value for 'within' qualifier
|
||||||
|
"""
|
||||||
def __init__(self, number_of_seconds):
|
def __init__(self, number_of_seconds):
|
||||||
if isinstance(number_of_seconds, IntegerConstant):
|
if isinstance(number_of_seconds, IntegerConstant):
|
||||||
self.number_of_seconds = number_of_seconds
|
self.number_of_seconds = number_of_seconds
|
||||||
|
@ -414,6 +636,12 @@ class WithinQualifier(_ExpressionQualifier):
|
||||||
|
|
||||||
|
|
||||||
class StartStopQualifier(_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):
|
def __init__(self, start_time, stop_time):
|
||||||
if isinstance(start_time, TimestampConstant):
|
if isinstance(start_time, TimestampConstant):
|
||||||
self.start_time = start_time
|
self.start_time = start_time
|
||||||
|
@ -433,6 +661,12 @@ class StartStopQualifier(_ExpressionQualifier):
|
||||||
|
|
||||||
|
|
||||||
class QualifiedObservationExpression(_PatternExpression):
|
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):
|
def __init__(self, observation_expression, qualifier):
|
||||||
self.observation_expression = observation_expression
|
self.observation_expression = observation_expression
|
||||||
self.qualifier = qualifier
|
self.qualifier = qualifier
|
||||||
|
|
|
@ -0,0 +1,379 @@
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
import stix2
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_comparison_expression():
|
||||||
|
|
||||||
|
exp = stix2.EqualityComparisonExpression("file:hashes.'SHA-256'",
|
||||||
|
stix2.HashConstant("aec070645fe53ee3b3763059376134f058cc337247c978add178b6ccdfb0019f", "SHA-256")) # noqa
|
||||||
|
|
||||||
|
assert str(exp) == "file:hashes.'SHA-256' = 'aec070645fe53ee3b3763059376134f058cc337247c978add178b6ccdfb0019f'"
|
||||||
|
|
||||||
|
|
||||||
|
def test_boolean_expression():
|
||||||
|
exp1 = stix2.MatchesComparisonExpression("email-message:from_ref.value",
|
||||||
|
stix2.StringConstant(".+\\@example\\.com$"))
|
||||||
|
exp2 = stix2.MatchesComparisonExpression("email-message:body_multipart[*].body_raw_ref.name",
|
||||||
|
stix2.StringConstant("^Final Report.+\\.exe$"))
|
||||||
|
exp = stix2.AndBooleanExpression([exp1, exp2])
|
||||||
|
|
||||||
|
assert str(exp) == "email-message:from_ref.value MATCHES '.+\\\\@example\\\\.com$' AND email-message:body_multipart[*].body_raw_ref.name MATCHES '^Final Report.+\\\\.exe$'" # noqa
|
||||||
|
|
||||||
|
|
||||||
|
def test_boolean_expression_with_parentheses():
|
||||||
|
exp1 = stix2.MatchesComparisonExpression(stix2.ObjectPath("email-message",
|
||||||
|
[stix2.ReferenceObjectPathComponent("from_ref"),
|
||||||
|
stix2.BasicObjectPathComponent("value")]),
|
||||||
|
stix2.StringConstant(".+\\@example\\.com$"))
|
||||||
|
exp2 = stix2.MatchesComparisonExpression("email-message:body_multipart[*].body_raw_ref.name",
|
||||||
|
stix2.StringConstant("^Final Report.+\\.exe$"))
|
||||||
|
exp = stix2.ParentheticalExpression(stix2.AndBooleanExpression([exp1, exp2]))
|
||||||
|
assert str(exp) == "(email-message:from_ref.value MATCHES '.+\\\\@example\\\\.com$' AND email-message:body_multipart[*].body_raw_ref.name MATCHES '^Final Report.+\\\\.exe$')" # noqa
|
||||||
|
|
||||||
|
|
||||||
|
def test_hash_followed_by_registryKey_expression_python_constant():
|
||||||
|
hash_exp = stix2.EqualityComparisonExpression("file:hashes.MD5",
|
||||||
|
stix2.HashConstant("79054025255fb1a26e4bc422aef54eb4", "MD5"))
|
||||||
|
o_exp1 = stix2.ObservationExpression(hash_exp)
|
||||||
|
reg_exp = stix2.EqualityComparisonExpression(stix2.ObjectPath("windows-registry-key", ["key"]),
|
||||||
|
stix2.StringConstant("HKEY_LOCAL_MACHINE\\foo\\bar"))
|
||||||
|
o_exp2 = stix2.ObservationExpression(reg_exp)
|
||||||
|
fb_exp = stix2.FollowedByObservationExpression([o_exp1, o_exp2])
|
||||||
|
para_exp = stix2.ParentheticalExpression(fb_exp)
|
||||||
|
qual_exp = stix2.WithinQualifier(300)
|
||||||
|
exp = stix2.QualifiedObservationExpression(para_exp, qual_exp)
|
||||||
|
assert str(exp) == "([file:hashes.MD5 = '79054025255fb1a26e4bc422aef54eb4'] FOLLOWEDBY [windows-registry-key:key = 'HKEY_LOCAL_MACHINE\\\\foo\\\\bar']) WITHIN 300 SECONDS" # noqa
|
||||||
|
|
||||||
|
|
||||||
|
def test_hash_followed_by_registryKey_expression():
|
||||||
|
hash_exp = stix2.EqualityComparisonExpression("file:hashes.MD5",
|
||||||
|
stix2.HashConstant("79054025255fb1a26e4bc422aef54eb4", "MD5"))
|
||||||
|
o_exp1 = stix2.ObservationExpression(hash_exp)
|
||||||
|
reg_exp = stix2.EqualityComparisonExpression(stix2.ObjectPath("windows-registry-key", ["key"]),
|
||||||
|
stix2.StringConstant("HKEY_LOCAL_MACHINE\\foo\\bar"))
|
||||||
|
o_exp2 = stix2.ObservationExpression(reg_exp)
|
||||||
|
fb_exp = stix2.FollowedByObservationExpression([o_exp1, o_exp2])
|
||||||
|
para_exp = stix2.ParentheticalExpression(fb_exp)
|
||||||
|
qual_exp = stix2.WithinQualifier(stix2.IntegerConstant(300))
|
||||||
|
exp = stix2.QualifiedObservationExpression(para_exp, qual_exp)
|
||||||
|
assert str(exp) == "([file:hashes.MD5 = '79054025255fb1a26e4bc422aef54eb4'] FOLLOWEDBY [windows-registry-key:key = 'HKEY_LOCAL_MACHINE\\\\foo\\\\bar']) WITHIN 300 SECONDS" # noqa
|
||||||
|
|
||||||
|
|
||||||
|
def test_file_observable_expression():
|
||||||
|
exp1 = stix2.EqualityComparisonExpression("file:hashes.'SHA-256'",
|
||||||
|
stix2.HashConstant(
|
||||||
|
"aec070645fe53ee3b3763059376134f058cc337247c978add178b6ccdfb0019f",
|
||||||
|
'SHA-256'))
|
||||||
|
exp2 = stix2.EqualityComparisonExpression("file:mime_type", stix2.StringConstant("application/x-pdf"))
|
||||||
|
bool_exp = stix2.ObservationExpression(stix2.AndBooleanExpression([exp1, exp2]))
|
||||||
|
assert str(bool_exp) == "[file:hashes.'SHA-256' = 'aec070645fe53ee3b3763059376134f058cc337247c978add178b6ccdfb0019f' AND file:mime_type = 'application/x-pdf']" # noqa
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("observation_class, op", [
|
||||||
|
(stix2.AndObservationExpression, 'AND'),
|
||||||
|
(stix2.OrObservationExpression, 'OR'),
|
||||||
|
])
|
||||||
|
def test_multiple_file_observable_expression(observation_class, op):
|
||||||
|
exp1 = stix2.EqualityComparisonExpression("file:hashes.'SHA-256'",
|
||||||
|
stix2.HashConstant(
|
||||||
|
"bf07a7fbb825fc0aae7bf4a1177b2b31fcf8a3feeaf7092761e18c859ee52a9c",
|
||||||
|
'SHA-256'))
|
||||||
|
exp2 = stix2.EqualityComparisonExpression("file:hashes.MD5",
|
||||||
|
stix2.HashConstant("cead3f77f6cda6ec00f57d76c9a6879f", "MD5"))
|
||||||
|
bool1_exp = stix2.OrBooleanExpression([exp1, exp2])
|
||||||
|
exp3 = stix2.EqualityComparisonExpression("file:hashes.'SHA-256'",
|
||||||
|
stix2.HashConstant(
|
||||||
|
"aec070645fe53ee3b3763059376134f058cc337247c978add178b6ccdfb0019f",
|
||||||
|
'SHA-256'))
|
||||||
|
op1_exp = stix2.ObservationExpression(bool1_exp)
|
||||||
|
op2_exp = stix2.ObservationExpression(exp3)
|
||||||
|
exp = observation_class([op1_exp, op2_exp])
|
||||||
|
assert str(exp) == "[file:hashes.'SHA-256' = 'bf07a7fbb825fc0aae7bf4a1177b2b31fcf8a3feeaf7092761e18c859ee52a9c' OR file:hashes.MD5 = 'cead3f77f6cda6ec00f57d76c9a6879f'] {} [file:hashes.'SHA-256' = 'aec070645fe53ee3b3763059376134f058cc337247c978add178b6ccdfb0019f']".format(op) # noqa
|
||||||
|
|
||||||
|
|
||||||
|
def test_root_types():
|
||||||
|
ast = stix2.ObservationExpression(
|
||||||
|
stix2.AndBooleanExpression(
|
||||||
|
[stix2.ParentheticalExpression(
|
||||||
|
stix2.OrBooleanExpression([
|
||||||
|
stix2.EqualityComparisonExpression("a:b", stix2.StringConstant("1")),
|
||||||
|
stix2.EqualityComparisonExpression("b:c", stix2.StringConstant("2"))])),
|
||||||
|
stix2.EqualityComparisonExpression(u"b:d", stix2.StringConstant("3"))]))
|
||||||
|
assert str(ast) == "[(a:b = '1' OR b:c = '2') AND b:d = '3']"
|
||||||
|
|
||||||
|
|
||||||
|
def test_artifact_payload():
|
||||||
|
exp1 = stix2.EqualityComparisonExpression("artifact:mime_type",
|
||||||
|
"application/vnd.tcpdump.pcap")
|
||||||
|
exp2 = stix2.MatchesComparisonExpression("artifact:payload_bin",
|
||||||
|
stix2.StringConstant("\\xd4\\xc3\\xb2\\xa1\\x02\\x00\\x04\\x00"))
|
||||||
|
and_exp = stix2.ObservationExpression(stix2.AndBooleanExpression([exp1, exp2]))
|
||||||
|
assert str(and_exp) == "[artifact:mime_type = 'application/vnd.tcpdump.pcap' AND artifact:payload_bin MATCHES '\\\\xd4\\\\xc3\\\\xb2\\\\xa1\\\\x02\\\\x00\\\\x04\\\\x00']" # noqa
|
||||||
|
|
||||||
|
|
||||||
|
def test_greater_than_python_constant():
|
||||||
|
exp1 = stix2.GreaterThanComparisonExpression("file:extensions.windows-pebinary-ext.sections[*].entropy", 7.0)
|
||||||
|
exp = stix2.ObservationExpression(exp1)
|
||||||
|
assert str(exp) == "[file:extensions.windows-pebinary-ext.sections[*].entropy > 7.0]"
|
||||||
|
|
||||||
|
|
||||||
|
def test_greater_than():
|
||||||
|
exp1 = stix2.GreaterThanComparisonExpression("file:extensions.windows-pebinary-ext.sections[*].entropy",
|
||||||
|
stix2.FloatConstant(7.0))
|
||||||
|
exp = stix2.ObservationExpression(exp1)
|
||||||
|
assert str(exp) == "[file:extensions.windows-pebinary-ext.sections[*].entropy > 7.0]"
|
||||||
|
|
||||||
|
|
||||||
|
def test_less_than():
|
||||||
|
exp = stix2.LessThanComparisonExpression("file:size", 1024)
|
||||||
|
assert str(exp) == "file:size < 1024"
|
||||||
|
|
||||||
|
|
||||||
|
def test_greater_than_or_equal():
|
||||||
|
exp = stix2.GreaterThanEqualComparisonExpression("file:size",
|
||||||
|
1024)
|
||||||
|
|
||||||
|
assert str(exp) == "file:size >= 1024"
|
||||||
|
|
||||||
|
|
||||||
|
def test_less_than_or_equal():
|
||||||
|
exp = stix2.LessThanEqualComparisonExpression("file:size",
|
||||||
|
1024)
|
||||||
|
assert str(exp) == "file:size <= 1024"
|
||||||
|
|
||||||
|
|
||||||
|
def test_not():
|
||||||
|
exp = stix2.LessThanComparisonExpression("file:size",
|
||||||
|
1024,
|
||||||
|
negated=True)
|
||||||
|
assert str(exp) == "file:size NOT < 1024"
|
||||||
|
|
||||||
|
|
||||||
|
def test_and_observable_expression():
|
||||||
|
exp1 = stix2.AndBooleanExpression([stix2.EqualityComparisonExpression("user-account:account_type",
|
||||||
|
"unix"),
|
||||||
|
stix2.EqualityComparisonExpression("user-account:user_id",
|
||||||
|
stix2.StringConstant("1007")),
|
||||||
|
stix2.EqualityComparisonExpression("user-account:account_login",
|
||||||
|
"Peter")])
|
||||||
|
exp2 = stix2.AndBooleanExpression([stix2.EqualityComparisonExpression("user-account:account_type",
|
||||||
|
"unix"),
|
||||||
|
stix2.EqualityComparisonExpression("user-account:user_id",
|
||||||
|
stix2.StringConstant("1008")),
|
||||||
|
stix2.EqualityComparisonExpression("user-account:account_login",
|
||||||
|
"Paul")])
|
||||||
|
exp3 = stix2.AndBooleanExpression([stix2.EqualityComparisonExpression("user-account:account_type",
|
||||||
|
"unix"),
|
||||||
|
stix2.EqualityComparisonExpression("user-account:user_id",
|
||||||
|
stix2.StringConstant("1009")),
|
||||||
|
stix2.EqualityComparisonExpression("user-account:account_login",
|
||||||
|
"Mary")])
|
||||||
|
exp = stix2.AndObservationExpression([stix2.ObservationExpression(exp1),
|
||||||
|
stix2.ObservationExpression(exp2),
|
||||||
|
stix2.ObservationExpression(exp3)])
|
||||||
|
assert str(exp) == "[user-account:account_type = 'unix' AND user-account:user_id = '1007' AND user-account:account_login = 'Peter'] AND [user-account:account_type = 'unix' AND user-account:user_id = '1008' AND user-account:account_login = 'Paul'] AND [user-account:account_type = 'unix' AND user-account:user_id = '1009' AND user-account:account_login = 'Mary']" # noqa
|
||||||
|
|
||||||
|
|
||||||
|
def test_invalid_and_observable_expression():
|
||||||
|
with pytest.raises(ValueError) as excinfo:
|
||||||
|
stix2.AndBooleanExpression([stix2.EqualityComparisonExpression("user-account:display_name",
|
||||||
|
"admin"),
|
||||||
|
stix2.EqualityComparisonExpression("email-addr:display_name",
|
||||||
|
stix2.StringConstant("admin"))])
|
||||||
|
assert "All operands to an 'AND' expression must have the same object type" in str(excinfo)
|
||||||
|
|
||||||
|
|
||||||
|
def test_hex():
|
||||||
|
exp_and = stix2.AndBooleanExpression([stix2.EqualityComparisonExpression("file:mime_type",
|
||||||
|
"image/bmp"),
|
||||||
|
stix2.EqualityComparisonExpression("file:magic_number_hex",
|
||||||
|
stix2.HexConstant("ffd8"))])
|
||||||
|
exp = stix2.ObservationExpression(exp_and)
|
||||||
|
assert str(exp) == "[file:mime_type = 'image/bmp' AND file:magic_number_hex = h'ffd8']"
|
||||||
|
|
||||||
|
|
||||||
|
def test_multiple_qualifiers():
|
||||||
|
exp_and = stix2.AndBooleanExpression([stix2.EqualityComparisonExpression("network-traffic:dst_ref.type",
|
||||||
|
"domain-name"),
|
||||||
|
stix2.EqualityComparisonExpression("network-traffic:dst_ref.value",
|
||||||
|
"example.com")])
|
||||||
|
exp_ob = stix2.ObservationExpression(exp_and)
|
||||||
|
qual_rep = stix2.RepeatQualifier(5)
|
||||||
|
qual_within = stix2.WithinQualifier(stix2.IntegerConstant(1800))
|
||||||
|
exp = stix2.QualifiedObservationExpression(stix2.QualifiedObservationExpression(exp_ob, qual_rep), qual_within)
|
||||||
|
assert str(exp) == "[network-traffic:dst_ref.type = 'domain-name' AND network-traffic:dst_ref.value = 'example.com'] REPEATS 5 TIMES WITHIN 1800 SECONDS" # noqa
|
||||||
|
|
||||||
|
|
||||||
|
def test_set_op():
|
||||||
|
exp = stix2.ObservationExpression(stix2.IsSubsetComparisonExpression("network-traffic:dst_ref.value",
|
||||||
|
"2001:0db8:dead:beef:0000:0000:0000:0000/64"))
|
||||||
|
assert str(exp) == "[network-traffic:dst_ref.value ISSUBSET '2001:0db8:dead:beef:0000:0000:0000:0000/64']"
|
||||||
|
|
||||||
|
|
||||||
|
def test_timestamp():
|
||||||
|
ts = stix2.TimestampConstant('2014-01-13T07:03:17Z')
|
||||||
|
assert str(ts) == "t'2014-01-13T07:03:17Z'"
|
||||||
|
|
||||||
|
|
||||||
|
def test_boolean():
|
||||||
|
exp = stix2.EqualityComparisonExpression("email-message:is_multipart",
|
||||||
|
True)
|
||||||
|
assert str(exp) == "email-message:is_multipart = true"
|
||||||
|
|
||||||
|
|
||||||
|
def test_binary():
|
||||||
|
const = stix2.BinaryConstant("dGhpcyBpcyBhIHRlc3Q=")
|
||||||
|
exp = stix2.EqualityComparisonExpression("artifact:payload_bin",
|
||||||
|
const)
|
||||||
|
assert str(exp) == "artifact:payload_bin = b'dGhpcyBpcyBhIHRlc3Q='"
|
||||||
|
|
||||||
|
|
||||||
|
def test_list():
|
||||||
|
exp = stix2.InComparisonExpression("process:name",
|
||||||
|
['proccy', 'proximus', 'badproc'])
|
||||||
|
assert str(exp) == "process:name IN ('proccy', 'proximus', 'badproc')"
|
||||||
|
|
||||||
|
|
||||||
|
def test_list2():
|
||||||
|
# alternate way to construct an "IN" Comparison Expression
|
||||||
|
exp = stix2.EqualityComparisonExpression("process:name",
|
||||||
|
['proccy', 'proximus', 'badproc'])
|
||||||
|
assert str(exp) == "process:name IN ('proccy', 'proximus', 'badproc')"
|
||||||
|
|
||||||
|
|
||||||
|
def test_invalid_constant_type():
|
||||||
|
with pytest.raises(ValueError) as excinfo:
|
||||||
|
stix2.EqualityComparisonExpression("artifact:payload_bin",
|
||||||
|
{'foo': 'bar'})
|
||||||
|
assert 'Unable to create a constant' in str(excinfo)
|
||||||
|
|
||||||
|
|
||||||
|
def test_invalid_integer_constant():
|
||||||
|
with pytest.raises(ValueError) as excinfo:
|
||||||
|
stix2.IntegerConstant('foo')
|
||||||
|
assert 'must be an integer' in str(excinfo)
|
||||||
|
|
||||||
|
|
||||||
|
def test_invalid_timestamp_constant():
|
||||||
|
with pytest.raises(ValueError) as excinfo:
|
||||||
|
stix2.TimestampConstant('foo')
|
||||||
|
assert 'Must be a datetime object or timestamp string' in str(excinfo)
|
||||||
|
|
||||||
|
|
||||||
|
def test_invalid_float_constant():
|
||||||
|
with pytest.raises(ValueError) as excinfo:
|
||||||
|
stix2.FloatConstant('foo')
|
||||||
|
assert 'must be a float' in str(excinfo)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("data, result", [
|
||||||
|
(True, True),
|
||||||
|
(False, False),
|
||||||
|
('True', True),
|
||||||
|
('False', False),
|
||||||
|
('true', True),
|
||||||
|
('false', False),
|
||||||
|
('t', True),
|
||||||
|
('f', False),
|
||||||
|
('T', True),
|
||||||
|
('F', False),
|
||||||
|
(1, True),
|
||||||
|
(0, False),
|
||||||
|
])
|
||||||
|
def test_boolean_constant(data, result):
|
||||||
|
boolean = stix2.BooleanConstant(data)
|
||||||
|
assert boolean.value == result
|
||||||
|
|
||||||
|
|
||||||
|
def test_invalid_boolean_constant():
|
||||||
|
with pytest.raises(ValueError) as excinfo:
|
||||||
|
stix2.BooleanConstant('foo')
|
||||||
|
assert 'must be a boolean' in str(excinfo)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("hashtype, data", [
|
||||||
|
('MD5', 'zzz'),
|
||||||
|
('ssdeep', 'zzz=='),
|
||||||
|
])
|
||||||
|
def test_invalid_hash_constant(hashtype, data):
|
||||||
|
with pytest.raises(ValueError) as excinfo:
|
||||||
|
stix2.HashConstant(data, hashtype)
|
||||||
|
assert 'is not a valid {} hash'.format(hashtype) in str(excinfo)
|
||||||
|
|
||||||
|
|
||||||
|
def test_invalid_hex_constant():
|
||||||
|
with pytest.raises(ValueError) as excinfo:
|
||||||
|
stix2.HexConstant('mm')
|
||||||
|
assert "must contain an even number of hexadecimal characters" in str(excinfo)
|
||||||
|
|
||||||
|
|
||||||
|
def test_invalid_binary_constant():
|
||||||
|
with pytest.raises(ValueError) as excinfo:
|
||||||
|
stix2.BinaryConstant('foo')
|
||||||
|
assert 'must contain a base64' in str(excinfo)
|
||||||
|
|
||||||
|
|
||||||
|
def test_escape_quotes_and_backslashes():
|
||||||
|
exp = stix2.MatchesComparisonExpression("file:name",
|
||||||
|
"^Final Report.+\\.exe$")
|
||||||
|
assert str(exp) == "file:name MATCHES '^Final Report.+\\\\.exe$'"
|
||||||
|
|
||||||
|
|
||||||
|
def test_like():
|
||||||
|
exp = stix2.LikeComparisonExpression("directory:path",
|
||||||
|
"C:\\Windows\\%\\foo")
|
||||||
|
assert str(exp) == "directory:path LIKE 'C:\\\\Windows\\\\%\\\\foo'"
|
||||||
|
|
||||||
|
|
||||||
|
def test_issuperset():
|
||||||
|
exp = stix2.IsSupersetComparisonExpression("ipv4-addr:value",
|
||||||
|
"198.51.100.0/24")
|
||||||
|
assert str(exp) == "ipv4-addr:value ISSUPERSET '198.51.100.0/24'"
|
||||||
|
|
||||||
|
|
||||||
|
def test_repeat_qualifier():
|
||||||
|
qual = stix2.RepeatQualifier(stix2.IntegerConstant(5))
|
||||||
|
assert str(qual) == 'REPEATS 5 TIMES'
|
||||||
|
|
||||||
|
|
||||||
|
def test_invalid_repeat_qualifier():
|
||||||
|
with pytest.raises(ValueError) as excinfo:
|
||||||
|
stix2.RepeatQualifier('foo')
|
||||||
|
assert 'is not a valid argument for a Repeat Qualifier' in str(excinfo)
|
||||||
|
|
||||||
|
|
||||||
|
def test_invalid_within_qualifier():
|
||||||
|
with pytest.raises(ValueError) as excinfo:
|
||||||
|
stix2.WithinQualifier('foo')
|
||||||
|
assert 'is not a valid argument for a Within Qualifier' in str(excinfo)
|
||||||
|
|
||||||
|
|
||||||
|
def test_startstop_qualifier():
|
||||||
|
qual = stix2.StartStopQualifier(stix2.TimestampConstant('2016-06-01T00:00:00Z'),
|
||||||
|
datetime.datetime(2017, 3, 12, 8, 30, 0))
|
||||||
|
assert str(qual) == "START t'2016-06-01T00:00:00Z' STOP t'2017-03-12T08:30:00Z'"
|
||||||
|
|
||||||
|
qual2 = stix2.StartStopQualifier(datetime.date(2016, 6, 1),
|
||||||
|
stix2.TimestampConstant('2016-07-01T00:00:00Z'))
|
||||||
|
assert str(qual2) == "START t'2016-06-01T00:00:00Z' STOP t'2016-07-01T00:00:00Z'"
|
||||||
|
|
||||||
|
|
||||||
|
def test_invalid_startstop_qualifier():
|
||||||
|
with pytest.raises(ValueError) as excinfo:
|
||||||
|
stix2.StartStopQualifier('foo',
|
||||||
|
stix2.TimestampConstant('2016-06-01T00:00:00Z'))
|
||||||
|
assert 'is not a valid argument for a Start/Stop Qualifier' in str(excinfo)
|
||||||
|
|
||||||
|
with pytest.raises(ValueError) as excinfo:
|
||||||
|
stix2.StartStopQualifier(datetime.date(2016, 6, 1),
|
||||||
|
'foo')
|
||||||
|
assert 'is not a valid argument for a Start/Stop Qualifier' in str(excinfo)
|
||||||
|
|
||||||
|
|
||||||
|
def test_make_constant_already_a_constant():
|
||||||
|
str_const = stix2.StringConstant('Foo')
|
||||||
|
result = stix2.patterns.make_constant(str_const)
|
||||||
|
assert result is str_const
|
Loading…
Reference in New Issue