Repository: oasis-open/cti-python-stix2

Issue:
"Location" type object throws an exception when using semantic equivalence
This is the object:

> {
> > 'administrative_area': 'US-DC', 
> > 'country': 'US', 
> > 'created': '2020-07-08T21:03:08.000Z', 
> > 'id': 'location--373ca831-cc20-4987-af8e-3e66edc5a294', 
> > 'modified': '2020-07-08T21:03:08.000Z', 
> > 'spec_version': '2.1', 
> > 'type': 'location'
> }

The location object has no "latitude" or "longitude", which leads to this exception:

> Traceback (most recent call last):
>   File "c:\users\zrush\stix_venv\cti-python-stix2\stix2\environment.py", line 245, in semantically_equivalent
>     method = weights[type1]["method"]
> KeyError: 'method'
> 
> During handling of the above exception, another exception occurred:
> 
> Traceback (most recent call last):
>   File "C:\Users\zrush\stix_venv\semantic-equivalence\bucket_objects.py", line 118, in <module>
>     buckets = bucketing(baseline)
>   File "C:\Users\zrush\stix_venv\semantic-equivalence\bucket_objects.py", line 83, in bucketing
>     new_bucket = build_bucket(base_obj, baseline[stix_type][baseline_iter + 1:])
>   File "C:\Users\zrush\stix_venv\semantic-equivalence\bucket_objects.py", line 63, in build_bucket
>     if env.semantically_equivalent(bucket_obj, possible_match) < THRESHOLD:
>   File "c:\users\zrush\stix_venv\cti-python-stix2\stix2\environment.py", line 260, in semantically_equivalent
>     contributing_score = w * comp_funct(obj1["latitude"], obj1["longitude"], obj2["latitude"], obj2["longitude"], threshold)
> KeyError: 'latitude'

Clearly, the location object needs to be checked for the attribute before trying to use it. I will look into a solution later today.

Discussion:
codecov-io: # [Codecov](https://codecov.io/gh/oasis-open/cti-python-stix2/pull/467?src=pr&el=h1) Report
> Merging [#467](https://codecov.io/gh/oasis-open/cti-python-stix2/pull/467?src=pr&el=desc) (7be1611) into [master](https://codecov.io/gh/oasis-open/cti-python-stix2/commit/cfb518a84b28339f1161ff11e7c239116e8cfa29?el=desc) (cfb518a) will **increase** coverage by `0.00%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/oasis-open/cti-python-stix2/pull/467/graphs/tree.svg?width=650&height=150&src=pr&token=vzHZ2LBVZq)](https://codecov.io/gh/oasis-open/cti-python-stix2/pull/467?src=pr&el=tree)

```diff
@@           Coverage Diff           @@
##           master     #467   +/-   ##
=======================================
  Coverage   89.32%   89.33%           
=======================================
  Files         144      144           
  Lines       16011    16020    +9     
=======================================
+ Hits        14302    14311    +9     
  Misses       1709     1709           
```


| [Impacted Files](https://codecov.io/gh/oasis-open/cti-python-stix2/pull/467?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [stix2/equivalence/object/\_\_init\_\_.py](https://codecov.io/gh/oasis-open/cti-python-stix2/pull/467/diff?src=pr&el=tree#diff-c3RpeDIvZXF1aXZhbGVuY2Uvb2JqZWN0L19faW5pdF9fLnB5) | `89.47% <100.00%> (+0.16%)` | :arrow_up: |
| [stix2/test/v21/test\_environment.py](https://codecov.io/gh/oasis-open/cti-python-stix2/pull/467/diff?src=pr&el=tree#diff-c3RpeDIvdGVzdC92MjEvdGVzdF9lbnZpcm9ubWVudC5weQ==) | `100.00% <100.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/oasis-open/cti-python-stix2/pull/467?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/oasis-open/cti-python-stix2/pull/467?src=pr&el=footer). Last update [cfb518a...7be1611](https://codecov.io/gh/oasis-open/cti-python-stix2/pull/467?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/stix2/equivalence/object/__init__.py b/stix2/equivalence/object/__init__.py
index 8333ceb..ec6cc42 100644
--- a/stix2/equivalence/object/__init__.py
+++ b/stix2/equivalence/object/__init__.py
@@ -68,7 +68,7 @@ def semantically_equivalent(obj1, obj2, prop_scores={}, **weight_dict):
             sum_weights = 0.0
 
             for prop in weights[type1]:
-                if check_property_present(prop, obj1, obj2) or prop == "longitude_latitude":
+                if check_property_present(prop, obj1, obj2):
                     w = weights[type1][prop][0]
                     comp_funct = weights[type1][prop][1]
 
@@ -117,7 +117,10 @@ def semantically_equivalent(obj1, obj2, prop_scores={}, **weight_dict):
 
 def check_property_present(prop, obj1, obj2):
     """Helper method checks if a property is present on both objects."""
-    if prop in obj1 and prop in obj2:
+    if prop == "longitude_latitude":
+        if all(x in obj1 and x in obj2 for x in ['latitude', 'longitude']):
+            return True
+    elif prop in obj1 and prop in obj2:
         return True
     return False
 


Test patch:
diff --git a/stix2/test/v21/test_environment.py b/stix2/test/v21/test_environment.py
index 95094fe..0da01d1 100644
--- a/stix2/test/v21/test_environment.py
+++ b/stix2/test/v21/test_environment.py
@@ -11,9 +11,9 @@ import stix2.exceptions
 from .constants import (
     ATTACK_PATTERN_ID, ATTACK_PATTERN_KWARGS, CAMPAIGN_ID, CAMPAIGN_KWARGS,
     FAKE_TIME, IDENTITY_ID, IDENTITY_KWARGS, INDICATOR_ID, INDICATOR_KWARGS,
-    LOCATION_ID, MALWARE_ID, MALWARE_KWARGS, RELATIONSHIP_IDS, REPORT_ID,
-    REPORT_KWARGS, THREAT_ACTOR_ID, THREAT_ACTOR_KWARGS, TOOL_ID, TOOL_KWARGS,
-    VULNERABILITY_ID, VULNERABILITY_KWARGS,
+    LOCATION_ID, LOCATION_KWARGS, MALWARE_ID, MALWARE_KWARGS, RELATIONSHIP_IDS,
+    REPORT_ID, REPORT_KWARGS, THREAT_ACTOR_ID, THREAT_ACTOR_KWARGS, TOOL_ID,
+    TOOL_KWARGS, VULNERABILITY_ID, VULNERABILITY_KWARGS,
 )
 
 FS_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "stix2_data")
@@ -495,26 +495,34 @@ def test_semantic_equivalence_on_same_indicator():
 
 
 def test_semantic_equivalence_on_same_location1():
-    LOCATION_KWARGS = dict(latitude=45, longitude=179)
-    loc1 = stix2.v21.Location(id=LOCATION_ID, **LOCATION_KWARGS)
-    loc2 = stix2.v21.Location(id=LOCATION_ID, **LOCATION_KWARGS)
+    location_kwargs = dict(latitude=45, longitude=179)
+    loc1 = stix2.v21.Location(id=LOCATION_ID, **location_kwargs)
+    loc2 = stix2.v21.Location(id=LOCATION_ID, **location_kwargs)
     env = stix2.Environment().semantically_equivalent(loc1, loc2)
     assert round(env) == 100
 
 
 def test_semantic_equivalence_on_same_location2():
-    LOCATION_KWARGS = dict(
+    location_kwargs = dict(
         latitude=38.889,
         longitude=-77.023,
         region="northern-america",
         country="us",
     )
-    loc1 = stix2.v21.Location(id=LOCATION_ID, **LOCATION_KWARGS)
-    loc2 = stix2.v21.Location(id=LOCATION_ID, **LOCATION_KWARGS)
+    loc1 = stix2.v21.Location(id=LOCATION_ID, **location_kwargs)
+    loc2 = stix2.v21.Location(id=LOCATION_ID, **location_kwargs)
     env = stix2.Environment().semantically_equivalent(loc1, loc2)
     assert round(env) == 100
 
 
+def test_semantic_equivalence_location_with_no_latlong():
+    loc_kwargs = dict(country="US", administrative_area="US-DC")
+    loc1 = stix2.v21.Location(id=LOCATION_ID, **LOCATION_KWARGS)
+    loc2 = stix2.v21.Location(id=LOCATION_ID, **loc_kwargs)
+    env = stix2.Environment().semantically_equivalent(loc1, loc2)
+    assert round(env) != 100
+
+
 def test_semantic_equivalence_on_same_malware():
     malw1 = stix2.v21.Malware(id=MALWARE_ID, **MALWARE_KWARGS)
     malw2 = stix2.v21.Malware(id=MALWARE_ID, **MALWARE_KWARGS)