Skip to content

Commit c213b58

Browse files
committed
harmonize tag obfuscation handling, tag obfuscation plugin now builtin
1 parent 1c7b087 commit c213b58

File tree

9 files changed

+145
-6
lines changed

9 files changed

+145
-6
lines changed

gsrest/__init__.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from gsrest.config import GSRestConfig, LoggingConfig
1515
from gsrest.dependencies import ConceptsCacheService, ServiceContainer
1616
from gsrest.plugins import get_subclass
17+
from gsrest.plugins.builtin.obfuscate_tags.obfuscate_tags import ObfuscateTags
1718

1819
CONFIG_FILE = "./instance/config.yaml"
1920

@@ -183,7 +184,26 @@ async def setup_services(app):
183184
app.app["plugin_contexts"] = {}
184185
app.app["request_config"] = {}
185186

187+
obfuscate_private_tags = any(
188+
1 for name in config.plugins if name.endswith("obfuscate_tags")
189+
)
190+
191+
if obfuscate_private_tags:
192+
app.app.logger.warning(
193+
"Tag obfuscation plugin enabled, using built-in version. Skipping load of external plugin."
194+
)
195+
builtinPlugin = ObfuscateTags
196+
name = f"{builtinPlugin.__module__}"
197+
app.app["plugins"].append(builtinPlugin)
198+
app.app["plugin_contexts"][name] = {}
199+
if hasattr(builtinPlugin, "setup"):
200+
app.app.cleanup_ctx.append(plugin_setup(builtinPlugin, name))
201+
186202
for name in config.plugins:
203+
if name.endswith("obfuscate_tags"):
204+
# already loaded builtin plugin
205+
continue
206+
187207
subcl = get_subclass(importlib.import_module(name))
188208
app.app["plugins"].append(subcl)
189209
app.app["plugin_contexts"][name] = {}

gsrest/dependencies.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515
from graphsenselib.tagstore.db import TagstoreDbAsync, Taxonomies
1616

1717
from gsrest.config import GSRestConfig
18+
from gsrest.plugins.builtin.obfuscate_tags.obfuscate_tags import (
19+
GROUPS_HEADER_NAME,
20+
OBFUSCATION_MARKER_GROUP,
21+
)
1822

1923

2024
class ConceptsCacheService(ConceptProtocol):
@@ -168,3 +172,7 @@ def get_tagstore_access_groups(request):
168172
def get_username(request) -> Optional[str]:
169173
"""Extract username from request, if available"""
170174
return request.headers.get("X-Consumer-Username", None)
175+
176+
177+
def should_obfuscate_private_tags(request) -> bool:
178+
return request.headers.get(GROUPS_HEADER_NAME, "") == OBFUSCATION_MARKER_GROUP

gsrest/plugins/__init__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@
44

55

66
class Plugin(abc.ABC):
7-
@abc.abstractclassmethod
7+
@classmethod
8+
@abc.abstractmethod
89
def before_request(cls, context, request: web.Request):
910
return request
1011

11-
@abc.abstractclassmethod
12+
@classmethod
13+
@abc.abstractmethod
1214
def before_response(cls, context, request: web.Request, result):
1315
return
1416

gsrest/plugins/builtin/__init__.py

Whitespace-only changes.

gsrest/plugins/builtin/obfuscate_tags/__init__.py

Whitespace-only changes.
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
from aiohttp import web
2+
from openapi_server.models.entity import Entity
3+
from openapi_server.models.address_tags import AddressTags
4+
from openapi_server.models.neighbor_entities import NeighborEntities
5+
from multidict import CIMultiDict
6+
from openapi_server.models.search_result_level1 import SearchResultLevel1
7+
from openapi_server.models.search_result_level2 import SearchResultLevel2
8+
from openapi_server.models.search_result_level3 import SearchResultLevel3
9+
from openapi_server.models.search_result_level4 import SearchResultLevel4
10+
from openapi_server.models.search_result_level5 import SearchResultLevel5
11+
from openapi_server.models.search_result_level6 import SearchResultLevel6
12+
from openapi_server.models.search_result_leaf import SearchResultLeaf
13+
14+
import re
15+
from gsrest.plugins import Plugin
16+
17+
from graphsenselib.tagstore.algorithms.obfuscate import (
18+
obfuscate_entity_actor,
19+
obfuscate_tag_if_not_public,
20+
)
21+
22+
GROUPS_HEADER_NAME = "X-Consumer-Groups"
23+
NO_OBFUSCATION_MARKER_GROUP = "private"
24+
OBFUSCATION_MARKER_GROUP = "obfuscate"
25+
26+
27+
class ObfuscateTags(Plugin):
28+
@classmethod
29+
def before_request(cls, context, request: web.Request):
30+
groups = [
31+
x.strip() for x in request.headers.get(GROUPS_HEADER_NAME, "").split(",")
32+
]
33+
34+
if NO_OBFUSCATION_MARKER_GROUP in groups:
35+
return request
36+
if "include_labels=true" in request.query_string.lower():
37+
return request
38+
if "/search" == request.path:
39+
return request
40+
if "/bulk" in request.path:
41+
return request
42+
if re.match(re.compile("/tags"), request.path):
43+
return request
44+
if re.match(re.compile("/[a-z]{3}/addresses/[^/]+$"), request.path):
45+
# to avoid loading actors for address
46+
return request
47+
48+
# setting group for request.
49+
headers = dict(request.headers)
50+
headers[GROUPS_HEADER_NAME] = OBFUSCATION_MARKER_GROUP
51+
headers = CIMultiDict(**headers)
52+
return request.clone(headers=headers)
53+
54+
@classmethod
55+
def before_response(cls, context, request: web.Request, result):
56+
# request.app.logger.debug(str(request.headers.get(GROUPS_HEADER_NAME, '')))
57+
groups = [
58+
x.strip() for x in request.headers.get(GROUPS_HEADER_NAME, "").split(",")
59+
]
60+
61+
if NO_OBFUSCATION_MARKER_GROUP in groups:
62+
return
63+
if isinstance(result, Entity):
64+
cls.obfuscate(result.best_address_tag)
65+
obfuscate_entity_actor(result)
66+
return
67+
if isinstance(result, AddressTags):
68+
cls.obfuscate(result.address_tags)
69+
return
70+
if isinstance(result, NeighborEntities):
71+
for neighbor in result.neighbors:
72+
cls.obfuscate(neighbor.entity.best_address_tag)
73+
obfuscate_entity_actor(neighbor.entity)
74+
if (
75+
isinstance(result, SearchResultLevel1)
76+
or isinstance(result, SearchResultLevel2)
77+
or isinstance(result, SearchResultLevel3)
78+
or isinstance(result, SearchResultLevel4)
79+
or isinstance(result, SearchResultLevel5)
80+
or isinstance(result, SearchResultLevel6)
81+
or isinstance(result, SearchResultLeaf)
82+
):
83+
if result.neighbor:
84+
cls.obfuscate(result.neighbor.entity.best_address_tag)
85+
if not isinstance(result, SearchResultLeaf) and result.paths:
86+
for path in result.paths:
87+
cls.before_response(context, request, path)
88+
return
89+
if isinstance(result, list):
90+
for r in result:
91+
cls.before_response(context, request, r)
92+
return
93+
94+
@classmethod
95+
def obfuscate(cls, tags):
96+
if not tags:
97+
return
98+
if isinstance(tags, list):
99+
for tag in tags:
100+
obfuscate_tag_if_not_public(tag)
101+
else:
102+
obfuscate_tag_if_not_public(tags)

gsrest/service/addresses_service.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
from typing import Optional
22

33
from graphsenselib.errors import BadUserInputException
4+
from graphsenselib.tagstore.algorithms.obfuscate import obfuscate_tag_if_not_public
45

56
from gsrest.dependencies import (
67
get_request_cache,
78
get_service_container,
89
get_tagstore_access_groups,
10+
should_obfuscate_private_tags,
911
)
1012
from gsrest.service import parse_page_int_optional
1113
from gsrest.translators import (
@@ -57,6 +59,11 @@ async def get_tag_summary_by_address(
5759
include_best_cluster_tag,
5860
include_pubkey_derived_tags=include_pubkey_derived_tags,
5961
only_propagate_high_confidence_actors=tag_summary_only_propagate_high_confidence_actors,
62+
tag_transformer=(
63+
None
64+
if not should_obfuscate_private_tags(request)
65+
else obfuscate_tag_if_not_public
66+
),
6067
)
6168

6269
return pydantic_to_openapi(pydantic_result)

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ exclude = [
9494
extend-select = ["T201"]
9595

9696
[tool.uv.sources]
97-
graphsense-lib = { git = "https://github.com/graphsense/graphsense-lib", rev = "v2.8.0-dev11" }
97+
graphsense-lib = { git = "https://github.com/graphsense/graphsense-lib", rev = "v2.8.0-rc5" }
9898

9999
[dependency-groups]
100100
dev = [

uv.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)