def test_apply_diff_merge_detetion_points():
    original_root = RootAnalysis()
    original_observable = original_root.add_observable("test", "test")

    modified_root = original_root.copy()
    modified_observable = modified_root.get_observable(original_observable)
    modified_observable.add_detection_point("test")

    target_root = original_root.copy()
    target_observable = target_root.get_observable(original_observable)

    assert not target_observable.has_detection_points()
    target_observable.apply_diff_merge(original_observable,
                                       modified_observable)
    assert target_observable.has_detection_points

    # exists before but not after
    original_root = RootAnalysis()
    original_observable = original_root.add_observable("test", "test")

    modified_root = original_root.copy()
    modified_observable = modified_root.get_observable(original_observable)

    target_root = original_root.copy()
    target_observable = target_root.get_observable(original_observable)

    original_observable.add_detection_point("test")

    assert not target_observable.has_detection_points()
    target_observable.apply_diff_merge(original_observable,
                                       modified_observable)
    assert not target_observable.has_detection_points()
def test_apply_diff_merge_directives():
    # does not exist before but exists after
    original_root = RootAnalysis()
    original_observable = original_root.add_observable("test", "test")
    modified_root = copy.deepcopy(original_root)
    modified_observable = modified_root.get_observable(original_observable)
    modified_observable.add_directive("test")

    target_root = RootAnalysis()
    observable = target_root.add_observable("test", "test")
    assert not observable.has_directive("test")
    observable.apply_diff_merge(original_observable, modified_observable)
    assert observable.has_directive("test")

    # exists before but not after
    original_root = RootAnalysis()
    original_observable = original_root.add_observable("test", "test")
    modified_root = copy.deepcopy(original_root)
    original_observable.add_directive("test")
    modified_observable = modified_root.get_observable(original_observable)

    target_root = RootAnalysis()
    observable = target_root.add_observable("test", "test")
    assert not observable.has_directive("test")
    observable.apply_diff_merge(original_observable, modified_observable)
    # should still not exist
    assert not observable.has_directive("test")
def test_observable_serialization():
    root = RootAnalysis()
    o_time = utc_now()
    target = root.add_observable("test", "other")
    o1 = root.add_observable(
        "test",
        "test",
        time=o_time,
        context="text context",
        directives=["directive1", "directive2"],
        limited_analysis=["limit1", "limit2"],
        excluded_analysis=["excluded1", "excluded2"],
        requested_analysis=["requested1", "requested2"],
    )

    o1.add_relationship("test", target)

    root = RootAnalysis.from_dict(root.to_model().dict())
    o2 = root.get_observable(o1)

    # should be two separate instances
    assert id(o1) != id(o2)

    assert o1.type == o2.type
    assert o1.value == o2.value
    assert o1.time == o2.time
    assert o1.context == o2.context
    assert o1.directives == o2.directives
    assert o1.limited_analysis == o2.limited_analysis
    assert o1.excluded_analysis == o2.excluded_analysis
    assert o1.requested_analysis == o2.requested_analysis
    assert o1.relationships == o2.relationships
def test_apply_diff_merge_excluded_analysis():
    # does not exist before but exists after
    original_root = RootAnalysis()
    original_observable = original_root.add_observable("test", "test")
    modified_root = copy.deepcopy(original_root)
    modified_observable = modified_root.get_observable(original_observable)
    modified_observable.exclude_analysis("test")

    target_root = RootAnalysis()
    observable = target_root.add_observable("test", "test")
    assert not observable.excluded_analysis
    observable.apply_diff_merge(original_observable, modified_observable)
    assert observable.excluded_analysis[0] == "test"

    # exists before but not after
    original_root = RootAnalysis()
    original_observable = original_root.add_observable("test", "test")
    modified_root = copy.deepcopy(original_root)
    modified_observable = modified_root.get_observable(original_observable)

    original_observable.exclude_analysis("test")

    target_root = RootAnalysis()
    observable = target_root.add_observable("test", "test")
    assert not observable.excluded_analysis
    observable.apply_diff_merge(original_observable, modified_observable)
    # should still not exist
    assert not observable.excluded_analysis
def test_apply_diff_merge_tags():
    original_root = RootAnalysis()
    original_observable = original_root.add_observable("test", "test")
    modified_root = original_root.copy()
    modified_observable = modified_root.get_observable(original_observable)
    modified_observable.add_tag("test")

    target_root = original_root.copy()
    target_observable = target_root.add_observable("test", "test")

    assert not target_observable.tags
    target_observable.apply_diff_merge(original_observable,
                                       modified_observable)
    assert target_observable.tags
    assert target_observable.tags[0] == "test"

    # exists before but not after
    original_root = RootAnalysis()
    original_observable = original_root.add_observable("test", "test")
    modified_root = original_root.copy()
    modified_observable = modified_root.get_observable(original_observable)

    target_root = original_root.copy()
    target_observable = target_root.add_observable("test", "test")

    original_observable.add_tag("test")

    assert not target_observable.tags
    target_observable.apply_diff_merge(original_observable,
                                       modified_observable)
    assert not target_observable.tags
def test_apply_merge_grouping_target():
    root = RootAnalysis()
    observable = root.add_observable("some_type", "some_value")

    target_root = RootAnalysis()
    target_observable = target_root.add_observable("some_type", "some_value")
    target_observable.grouping_target = True

    assert not observable.grouping_target
    observable.apply_merge(target_observable)
    assert observable.grouping_target
def test_apply_merge_directives():
    root = RootAnalysis()
    observable = root.add_observable("some_type", "some_value")

    target_root = RootAnalysis()
    target_observable = target_root.add_observable("some_type", "some_value")
    target_observable.add_directive("some_directive")

    assert not observable.has_directive("some_directive")
    observable.apply_merge(target_observable)
    assert observable.has_directive("some_directive")
def test_apply_merge_detetion_points():
    root = RootAnalysis()
    observable = root.add_observable("test", "test")

    target_root = RootAnalysis()
    target_observable = target_root.add_observable("test", "test")
    target_observable.add_detection_point("test")

    assert not observable.has_detection_points()
    observable.apply_merge(target_observable)
    assert observable.has_detection_points
def test_search_down():
    amt = AnalysisModuleType("test", "")
    root = RootAnalysis()
    observable = root.add_observable("test", "test")
    analysis = observable.add_analysis(type=amt)
    child = analysis.add_observable("test", "child")
    observable_2 = root.add_observable("test", "other")

    assert search_down(child, lambda obj: obj == analysis) == analysis
    assert search_down(child, lambda obj: obj == observable) == observable
    assert search_down(child, lambda obj: obj == root) == root
    assert search_down(child, lambda obj: False) is None
def test_apply_merge_tags():
    root = RootAnalysis()
    observable = root.add_observable("some_type", "some_value")

    target_root = RootAnalysis()
    target_observable = target_root.add_observable("some_type", "some_value")
    target_observable.add_tag("test")

    assert not observable.tags
    observable.apply_merge(target_observable)
    assert observable.tags
    assert observable.tags[0] == "test"
def test_apply_merge_excluded_analysis():
    root = RootAnalysis()
    observable = root.add_observable("some_type", "some_value")

    target_root = RootAnalysis()
    target_observable = target_root.add_observable("some_type", "some_value")
    target_observable.exclude_analysis("some_module")

    assert not observable.excluded_analysis
    observable.apply_merge(target_observable)
    assert observable.excluded_analysis
    assert observable.excluded_analysis[0] == "some_module"
def test_apply_merge_links():
    root = RootAnalysis()
    observable = root.add_observable("some_type", "some_value")

    # test the case where the link target does not exist in the root analysis

    target_root = RootAnalysis()
    target_observable = target_root.add_observable("some_type", "some_value")
    linked_observable = target_root.add_observable("other_type", "other_value")
    target_observable.add_link(linked_observable)

    assert not observable.links
    observable.apply_merge(target_observable)
    assert observable.links
    assert observable.links[0] == linked_observable

    # also test the case where the link target already exists in the root analysis

    root = RootAnalysis()
    observable = root.add_observable("some_type", "some_value")
    root.add_observable("other_type", "other_value")

    target_root = RootAnalysis()
    target_observable = target_root.add_observable("some_type", "some_value")
    linked_observable = target_root.add_observable("other_type", "other_value")
    target_observable.add_link(linked_observable)

    assert not observable.links
    observable.apply_merge(target_observable)
    assert observable.links
    assert observable.links[0] == linked_observable
def test_apply_merge_relationships():
    root = RootAnalysis()
    observable = root.add_observable("some_type", "some_value")

    # test the case where the relationship target does not exist in the root analysis

    target_root = RootAnalysis()
    target_observable = target_root.add_observable("some_type", "some_value")
    r_observable = target_root.add_observable("other_type", "other_value")
    target_observable.add_relationship("downloaded_from", r_observable)

    assert not observable.relationships
    observable.apply_merge(target_observable)
    assert observable.relationships["downloaded_from"] == [r_observable]

    # also test the case where the relationship target already exists in the root analysis

    root = RootAnalysis()
    observable = root.add_observable("some_type", "some_value")
    root.add_observable("other_type", "other_value")

    # test the case where the relationship target does not exist in the root analysis

    target_root = RootAnalysis()
    target_observable = target_root.add_observable("some_type", "some_value")
    r_observable = target_root.add_observable("other_type", "other_value")
    target_observable.add_relationship("downloaded_from", r_observable)

    assert not observable.relationships
    observable.apply_merge(target_observable)
    assert observable.relationships["downloaded_from"] == [r_observable]
def test_apply_merge_redirection():
    root = RootAnalysis()
    observable = root.add_observable("some_type", "some_value")

    # test the case where the redirection target does not exist in the root analysis

    target_root = RootAnalysis()
    target_observable = target_root.add_observable("some_type", "some_value")
    target_redirection_observable = target_root.add_observable(
        "other_type", "other_value")
    target_observable.redirection = target_redirection_observable

    assert observable.redirection is None
    observable.apply_merge(target_observable)
    assert observable.redirection == target_redirection_observable

    # also test the case where the redirection target already exists in the root analysis

    root = RootAnalysis()
    observable = root.add_observable("some_type", "some_value")
    root.add_observable("other_type", "other_value")

    target_root = RootAnalysis()
    target_observable = target_root.add_observable("some_type", "some_value")
    target_redirection_observable = target_root.add_observable(
        "other_type", "other_value")
    target_observable.redirection = target_redirection_observable

    assert observable.redirection is None
    observable.apply_merge(target_observable)
    assert observable.redirection == target_redirection_observable
def test_apply_merge_analysis():
    amt = AnalysisModuleType("test", "")
    root = RootAnalysis()
    observable = root.add_observable("some_type", "some_value")

    target_root = RootAnalysis()
    target_observable = target_root.add_observable("some_type", "some_value")
    target_observable.add_analysis(Analysis(type=amt, details={"test":
                                                               "test"}))

    assert not observable.analysis
    observable.apply_merge(target_observable)
    assert observable.analysis
    assert observable.get_analysis("test") is not None
    assert observable.get_analysis("test")._details == {"test": "test"}
def test_apply_diff_merge_grouping_target():
    # grouping target modified
    original_root = RootAnalysis()
    original_observable = original_root.add_observable("test", "test")
    modified_root = copy.deepcopy(original_root)
    modified_observable = modified_root.get_observable(original_observable)
    modified_observable.grouping_target = True

    target_root = RootAnalysis()
    target_observable = target_root.add_observable("test", "test")

    assert not target_observable.grouping_target
    target_observable.apply_diff_merge(original_observable,
                                       modified_observable)
    assert target_observable.grouping_target
def test_add_analysis():
    amt = AnalysisModuleType("test", "")
    root = RootAnalysis()
    observable = root.add_observable("test", "test")

    # test adding an Analysis object
    analysis = Analysis(details={"hello": "world"}, type=amt)
    result = observable.add_analysis(analysis)
    assert result == analysis

    # test adding just a details, type
    root = RootAnalysis()
    observable = root.add_observable("test", "test")
    analysis = observable.add_analysis(details={"hello": "world"}, type=amt)
    assert result == analysis
def test_root_all_detection_points():
    amt = AnalysisModuleType("test", "")
    root = RootAnalysis()
    observable = root.add_observable("test", "test")
    analysis = observable.add_analysis(type=amt)
    observable_2 = analysis.add_observable("test", "test2")

    root.add_detection_point("test")
    assert root.all_detection_points == [DetectionPoint("test")]
    observable.add_detection_point("test")
    assert root.all_detection_points == [
        DetectionPoint("test"), DetectionPoint("test")
    ]
    analysis.add_detection_point("test")
    assert root.all_detection_points == [
        DetectionPoint("test"),
        DetectionPoint("test"),
        DetectionPoint("test")
    ]
    observable_2.add_detection_point("test")
    assert root.all_detection_points == [
        DetectionPoint("test"),
        DetectionPoint("test"),
        DetectionPoint("test"),
        DetectionPoint("test"),
    ]
def test_recurse_down():
    amt = AnalysisModuleType("test", "")
    root = RootAnalysis()
    observable = root.add_observable("test", "test")
    analysis = observable.add_analysis(type=amt)
    child = analysis.add_observable("test", "child")
    observable_2 = root.add_observable("test", "other")

    # we'll build a list of the nodes we visit
    result = []

    def _callback(node):
        result.append(node)

    # R -> O1 -> A -> O2
    #   -> O3
    recurse_down(root, _callback)
    assert result == [root]

    result = []
    recurse_down(observable, _callback)
    assert result == [observable, root]

    result = []
    recurse_down(analysis, _callback)
    assert result == [analysis, observable, root]

    result = []
    recurse_down(child, _callback)
    assert result == [child, analysis, observable, root]

    result = []
    recurse_down(observable_2, _callback)
    assert result == [observable_2, root]

    # R -> O1 -> A -> O1
    root = RootAnalysis()
    observable = root.add_observable("test", "test")
    analysis = observable.add_analysis(type=amt)
    # this points to the same observable as before
    child = analysis.add_observable("test", "test")

    result = []
    recurse_down(observable, _callback)
    assert len(result) == 3
    # (order is random when there are multiple paths to root)
    assert analysis in result and observable in result and root in result
def test_observable_properties():
    amt = AnalysisModuleType("test", "")
    root = RootAnalysis()
    observable = root.add_observable("test", "test")
    analysis = observable.add_analysis(type=amt)

    assert observable.all_analysis == [analysis]
    assert observable.children == [analysis]
def test_has_observable():
    root = RootAnalysis()
    observable = root.add_observable("test", "test")
    assert root.has_observable("test", "test")
    assert not root.has_observable("test", "t3st")
    assert root.has_observable(Observable("test", "test"))
    assert not root.has_observable(Observable("t3st", "test"))
    assert not root.has_observable(Observable("test", "t3st"))
def test_root_analysis_get_observables_by_type():
    amt = AnalysisModuleType("test", "")
    root = RootAnalysis()
    observable_1 = root.add_observable("test", "test_1")
    observable_2 = root.add_observable("test", "test_2")
    observable_3 = root.add_observable("test_3", "test_3")
    analysis = observable_3.add_analysis(type=amt)
    observable_4 = analysis.add_observable("test_4", "test_4")

    assert root.get_observables_by_type("test") == [observable_1, observable_2]
    assert root.get_observables_by_type("test_3") == [observable_3]
    assert root.get_observables_by_type("test_4") == [observable_4]
    assert root.get_observables_by_type("unknown") == []

    assert root.get_observable_by_type("test") in [observable_1, observable_2]
    assert root.get_observable_by_type("test_3") == observable_3
    assert root.get_observable_by_type("test_4") == observable_4
    assert root.get_observable_by_type("unknown") is None
def test_root_all():
    amt = AnalysisModuleType("test", "")
    root = RootAnalysis()
    observable = root.add_observable("test", "test")
    analysis = observable.add_analysis(type=amt)
    observable_2 = analysis.add_observable("test", "test2")

    assert sorted(root.all) == sorted(
        [root, analysis, observable, observable_2])
def test_add_duplicate_analysis():
    amt = AnalysisModuleType("test", "")
    root = RootAnalysis()
    observable = root.add_observable("test", "test")
    analysis = observable.add_analysis(type=amt)

    # adding the same analysis twice will fail
    with pytest.raises(ValueError):
        observable.add_analysis(type=amt)
def test_add_link():
    root = RootAnalysis()
    observable_1 = root.add_observable("test", "test1")
    observable_2 = root.add_observable("test", "test2")
    observable_3 = root.add_observable("test", "test3")

    observable_2.add_link(observable_1)
    assert observable_2.links == [observable_1]
    observable_2.add_link(observable_3)
    assert sorted(observable_2.links) == sorted([observable_1, observable_3])

    observable_2.add_tag("test")
    assert observable_2.has_tag("test")
    assert observable_1.has_tag("test")
    assert observable_3.has_tag("test")

    with pytest.raises(ValueError):
        # cannot do this since 2 already points to 1
        observable_1.add_link(observable_2)
def test_root_copy():
    root = RootAnalysis()
    observable = root.add_observable("test", "test")
    amt = AnalysisModuleType("test", "")
    analysis = observable.add_analysis(type=amt, details={"test": "test"})

    root_copy = root.copy()
    observable_copy = root_copy.get_observable(observable)
    assert observable_copy == observable
    assert not (observable_copy is observable)
    analysis_copy = observable_copy.get_analysis(amt)
    assert analysis_copy == analysis
    assert not (analysis_copy is analysis)
def test_relationships():
    root = RootAnalysis()
    observable_1 = root.add_observable("test", "test1")
    observable_2 = root.add_observable("test", "test2")

    observable_1.add_relationship("test", observable_2)
    assert observable_1.has_relationship("test")
    assert observable_1.has_relationship("test", observable_2)

    assert observable_1.get_relationships_by_type("test") == [observable_2]
    assert observable_1.get_relationship_by_type("test") == observable_2
    assert observable_1.get_relationships_by_type("unknown") == []
    assert observable_1.get_relationship_by_type("unknown") is None
def test_apply_diff_merge_redirection():
    # test redirection created
    original_root = RootAnalysis()
    original_observable = original_root.add_observable("test", "test")
    modified_root = copy.deepcopy(original_root)
    modified_observable = modified_root.get_observable(original_observable)
    modified_observable.redirection = modified_root.add_observable(
        "target", "target")

    target_root = RootAnalysis()
    target_observable = target_root.add_observable("test", "test")

    assert target_observable.redirection is None
    target_observable.apply_diff_merge(original_observable,
                                       modified_observable)
    assert target_root.get_observable(modified_observable.redirection)
    assert target_observable.redirection == target_root.get_observable(
        modified_observable.redirection)

    # test redirection modified
    original_root = RootAnalysis()
    original_observable = original_root.add_observable("test", "test")
    original_observable.redirection = original_root.add_observable(
        "target", "target")

    modified_root = copy.deepcopy(original_root)
    modified_observable = modified_root.get_observable(original_observable)
    modified_observable.redirection = modified_root.add_observable(
        "other", "other")

    target_root = RootAnalysis()
    target_observable = target_root.add_observable("test", "test")

    assert target_observable.redirection is None
    target_observable.apply_diff_merge(original_observable,
                                       modified_observable)
    assert target_root.get_observable(modified_observable.redirection)
    assert target_observable.redirection == target_root.get_observable(
        modified_observable.redirection)
def test_apply_diff_merge_links():
    # does not exist before but exists after
    original_root = RootAnalysis()
    original_observable = original_root.add_observable("test", "test")
    modified_root = copy.deepcopy(original_root)
    modified_observable = modified_root.get_observable(original_observable)
    link_target = modified_root.add_observable("target", "target")
    modified_observable.add_link(link_target)

    target_root = RootAnalysis()
    observable = target_root.add_observable("test", "test")
    assert not observable.links
    observable.apply_diff_merge(original_observable, modified_observable)
    linked_observable = target_root.get_observable(link_target)
    assert linked_observable
    assert observable.links[0] == linked_observable

    # exists before but not after
    original_root = RootAnalysis()
    original_observable = original_root.add_observable("test", "test")
    modified_root = copy.deepcopy(original_root)
    modified_observable = modified_root.get_observable(original_observable)

    link_target = original_root.add_observable("target", "target")
    original_observable.add_link(link_target)

    target_root = RootAnalysis()
    observable = target_root.add_observable("test", "test")
    assert not observable.links
    observable.apply_diff_merge(original_observable, modified_observable)
    # should still not exist
    assert not observable.links

    # exists before and after
    original_root = RootAnalysis()
    original_observable = original_root.add_observable("test", "test")
    link_target = original_root.add_observable("target", "target")
    original_observable.add_link(link_target)

    modified_root = copy.deepcopy(original_root)
    modified_observable = modified_root.get_observable(original_observable)

    target_root = RootAnalysis()
    observable = target_root.add_observable("test", "test")
    assert not observable.links
    observable.apply_diff_merge(original_observable, modified_observable)
    # should still not exist
    assert not observable.links
def test_root_analysis_serialization():
    root = RootAnalysis(
        tool="test",
        tool_instance="test",
        alert_type="test",
        desc="test",
        event_time=datetime.datetime.now(),
        name="test",
        analysis_mode="test",
        queue="test",
        instructions="test",
    )

    amt = AnalysisModuleType("test", "")
    observable = root.add_observable("test", "test")
    analysis = observable.add_analysis(type=amt, details={"test": "test"})
    root.add_detection_point("test")

    new_root = RootAnalysis.from_dict(root.to_dict())
    assert root == new_root
    assert root.tool == new_root.tool
    assert root.tool_instance == new_root.tool
    assert root.alert_type == new_root.alert_type
    assert root.description == new_root.description
    assert root.event_time == new_root.event_time
    assert root.name == new_root.name
    assert root.analysis_mode == new_root.analysis_mode
    assert root.queue == new_root.queue
    assert root.instructions == new_root.instructions
    assert root.detections == new_root.detections

    # the observable property for the root should always be None
    assert root.observable is None
    assert len(root.observables) == 1

    new_root = RootAnalysis.from_json(root.to_json())
    assert root == new_root
    assert root.tool == new_root.tool
    assert root.tool_instance == new_root.tool
    assert root.alert_type == new_root.alert_type
    assert root.description == new_root.description
    assert root.event_time == new_root.event_time
    assert root.name == new_root.name
    assert root.analysis_mode == new_root.analysis_mode
    assert root.queue == new_root.queue
    assert root.instructions == new_root.instructions

    # the observable property for the root should always be None
    assert root.observable is None
    assert len(root.observables) == 1