diff --git a/pipeline/fiftyone_pipeline_cloudrequestengine/examples/tests/test_cloud_mixed_web_offline.py b/pipeline/fiftyone_pipeline_cloudrequestengine/examples/tests/test_cloud_mixed_web_offline.py
index 47fe209e..8ba78fef 100644
--- a/pipeline/fiftyone_pipeline_cloudrequestengine/examples/tests/test_cloud_mixed_web_offline.py
+++ b/pipeline/fiftyone_pipeline_cloudrequestengine/examples/tests/test_cloud_mixed_web_offline.py
@@ -302,3 +302,14 @@ def test_licence_key_gets_an_engine_only_for_what_it_asks_for(cloud):
def test_licence_key_never_reaches_the_page(cloud):
cloud(EVERY_SECTION)
assert "a-licence-key" not in page_for(license_key="a-licence-key")
+
+
+def test_the_web_request_is_not_sent_to_the_cloud(cloud):
+ # The web integration adds the request itself as server.http-request for
+ # the usage sharing element, and it is never evidence another element
+ # reads.
+ stub = cloud(EVERY_SECTION)
+ page_for(resource_key="a-resource-key")
+
+ url, data = stub.requests[-1]
+ assert not any("http-request" in key for key in data), data
diff --git a/pipeline/fiftyone_pipeline_core/src/fiftyone_pipeline_core/evidence.py b/pipeline/fiftyone_pipeline_core/src/fiftyone_pipeline_core/evidence.py
index f7067647..02b45ce7 100644
--- a/pipeline/fiftyone_pipeline_core/src/fiftyone_pipeline_core/evidence.py
+++ b/pipeline/fiftyone_pipeline_core/src/fiftyone_pipeline_core/evidence.py
@@ -20,6 +20,11 @@
# such notice(s) shall fulfill the requirements of that article.
# *********************************************************************
+# The key the web integration gives the web request itself, which the usage
+# sharing element reads. No element uses it as evidence, so filter() always
+# sets it aside, whatever the elements' filters accept.
+HTTP_REQUEST_KEY = "server.http-request"
+
class Evidence:
def __init__(self, flowdata):
@@ -32,6 +37,7 @@ def __init__(self, flowdata):
self.evidence = {}
self.evidence_filtered = False
self.flowdata = flowdata
+ self.unaccepted = {}
def add(self, key, value):
"""!
@@ -89,10 +95,25 @@ def get_all(self):
return self.evidence
+ def get_unaccepted(self):
+ """!
+ The evidence filter() set aside, being the keys no FlowElement in
+ the pipeline accepts and the web request itself. The usage sharing
+ element accepts no evidence, and shares what it finds here and in
+ get_all().
+
+ @rtype: dict
+ @return: The evidence set aside, empty until filter() runs
+ """
+
+ return self.unaccepted
+
def filter(self) -> None:
"""!
Filter evidence using `filter_evidence_key` and
- `filter_evidence` methods of each FlowElement in the pipeline
+ `filter_evidence` methods of each FlowElement in the pipeline. The
+ keys that do not pass, and the web request under HTTP_REQUEST_KEY,
+ move to get_unaccepted().
"""
if self.evidence_filtered:
@@ -100,11 +121,11 @@ def filter(self) -> None:
# Loop over a copy of the keys, as keys are removed on the way.
for key in list(self.evidence):
- if not any([
+ if str(key).lower() == HTTP_REQUEST_KEY or not any([
flow_element.filter_evidence_key(key)
for flow_element
in self.flowdata.pipeline.flow_elements
]):
- del self.evidence[key]
+ self.unaccepted[key] = self.evidence.pop(key)
self.evidence_filtered = True
diff --git a/pipeline/fiftyone_pipeline_core/src/fiftyone_pipeline_core/web.py b/pipeline/fiftyone_pipeline_core/src/fiftyone_pipeline_core/web.py
index d3f96cf0..d51ca012 100644
--- a/pipeline/fiftyone_pipeline_core/src/fiftyone_pipeline_core/web.py
+++ b/pipeline/fiftyone_pipeline_core/src/fiftyone_pipeline_core/web.py
@@ -20,13 +20,30 @@
# such notice(s) shall fulfill the requirements of that article.
# *********************************************************************
-from flask import request
+from types import MappingProxyType
+
+from flask import has_request_context, request, session
+
+from .evidence import HTTP_REQUEST_KEY
+
+def request_line_path(request):
+
+ """!
+ The path of the request line as the server received it, when the server
+ says what that was, and otherwise the path Flask decoded.
+ """
+
+ raw = request.environ.get("RAW_URI") or request.environ.get("REQUEST_URI")
+ if raw:
+ return raw.split("?", 1)[0].split("#", 1)[0]
+ return request.path
def webevidence(request):
"""!
- Get evidence from a web request (gets headers, cookies, query parameters
- and the fields of a posted form)
+ Get evidence from a web request (gets headers, cookies, session values,
+ query parameters and the fields of a posted form, and the request itself
+ as server.http-request)
@type request: Request
@param request: A Request object
@@ -59,6 +76,14 @@ def webevidence(request):
webevidence["query." + field] = value
+ # Values stored in the session. They are read without marking the session
+ # as accessed, which would add a Vary: Cookie header to the response, and
+ # only plain values are taken.
+ if has_request_context():
+ for name, value in dict.items(session._get_current_object()):
+ if isinstance(value, (str, int, float, bool)):
+ webevidence["session." + name] = value
+
webevidence["server.client-ip"] = request.remote_addr
webevidence["server.host-ip"] = request.host
@@ -68,6 +93,17 @@ def webevidence(request):
else:
webevidence["header.protocol"] = "http"
+ # The request itself, for the usage sharing element, which accepts no
+ # evidence and shares what it finds. A read-only view of the parts above
+ # and the method and path of the request line, keyed as evidence is. It
+ # leaves out the Cookie header, which cookies already cover. Evidence
+ # keeps it from every other element.
+ view = {key: value for key, value in webevidence.items()
+ if key not in ("header.cookie", "server.host-ip")}
+ view["server.request-method"] = request.method
+ view["server.request-path"] = request_line_path(request)
+ webevidence[HTTP_REQUEST_KEY] = MappingProxyType(view)
+
return webevidence
def set_response_header(flowData, response):
diff --git a/pipeline/fiftyone_pipeline_core/tests/test_evidence_unaccepted.py b/pipeline/fiftyone_pipeline_core/tests/test_evidence_unaccepted.py
new file mode 100644
index 00000000..caf0edad
--- /dev/null
+++ b/pipeline/fiftyone_pipeline_core/tests/test_evidence_unaccepted.py
@@ -0,0 +1,110 @@
+# *********************************************************************
+# This Original Work is copyright of 51 Degrees Mobile Experts Limited.
+# Copyright 2026 51 Degrees Mobile Experts Limited, Davidson House,
+# Forbury Square, Reading, Berkshire, United Kingdom RG1 3EU.
+#
+# This Original Work is licensed under the European Union Public Licence
+# (EUPL) v.1.2 and is subject to its terms as set out below.
+#
+# If a copy of the EUPL was not distributed with this file, You can obtain
+# one at https://opensource.org/licenses/EUPL-1.2.
+#
+# The 'Compatible Licences' set out in the Appendix to the EUPL (as may be
+# amended by the European Commission) shall be deemed incompatible for
+# the purposes of the Work and the provisions of the compatibility
+# clause in Article 5 of the EUPL shall not apply.
+#
+# If using the Work as, or as part of, a network application, by
+# including the attribution notice(s) required under Article 5 of the EUPL
+# in the end user terms of the application under an appropriate heading,
+# such notice(s) shall fulfill the requirements of that article.
+# *********************************************************************
+
+"""The evidence a pipeline's elements do not accept, and the web request the
+web integration adds as server.http-request."""
+
+import unittest
+
+from flask import Flask, request, session
+
+from fiftyone_pipeline_core.pipelinebuilder import PipelineBuilder
+from fiftyone_pipeline_core.web import webevidence
+
+from .classes.exampleflowelement1 import ExampleFlowElement1
+
+# No JavaScript or set header elements, so the only filter is that of
+# ExampleFlowElement1, which accepts header.user-agent.
+NO_DEFAULT_ELEMENTS = {
+ "add_javascript_builder": False,
+ "use_setheader_properties": False,
+}
+
+
+class UnacceptedEvidenceTests(unittest.TestCase):
+
+ def test_evidence_no_element_accepts_is_set_aside(self):
+ pipeline = PipelineBuilder(NO_DEFAULT_ELEMENTS) \
+ .add(ExampleFlowElement1()).build()
+ flowdata = pipeline.create_flowdata()
+ flowdata.evidence.add("header.user-agent", "a browser")
+ flowdata.evidence.add("header.x-test", "set aside")
+ flowdata.process()
+
+ self.assertEqual(
+ {"header.user-agent": "a browser"}, flowdata.evidence.get_all())
+ self.assertEqual(
+ {"header.x-test": "set aside"},
+ flowdata.evidence.get_unaccepted())
+
+ def test_the_web_request_is_added_and_set_aside(self):
+ """No element reads server.http-request as evidence, even an element
+ that accepts every key, as the JavaScript builder does."""
+
+ pipeline = PipelineBuilder().add(ExampleFlowElement1()).build()
+ app = Flask(__name__)
+ with app.test_request_context(
+ "/?campaign=a", headers={"User-Agent": "a browser",
+ "X-Test": "a header"}):
+ evidence = webevidence(request)
+ flowdata = pipeline.create_flowdata()
+ flowdata.evidence.add_from_dict(evidence)
+ flowdata.process()
+
+ self.assertNotIn("server.http-request", flowdata.evidence.get_all())
+ view = flowdata.evidence.get_unaccepted()["server.http-request"]
+ self.assertEqual("a header", view["header.x-test"])
+ self.assertEqual("a browser", view["header.user-agent"])
+ self.assertEqual("a", view["query.campaign"])
+ # A read-only view of the request, not evidence an element can
+ # change.
+ with self.assertRaises(TypeError):
+ view["header.x-test"] = "changed"
+
+ def test_the_web_request_has_every_part_the_specification_names(self):
+ app = Flask(__name__)
+ app.secret_key = "a secret"
+ with app.test_request_context(
+ "/page?campaign=a",
+ headers={"User-Agent": "a browser",
+ "Cookie": "sessionid=cookievalue"},
+ environ_base={"REMOTE_ADDR": "203.0.113.5"}):
+ session["51D_one"] = "first"
+ session["theme"] = "dark"
+ view = webevidence(request)["server.http-request"]
+
+ self.assertEqual("a browser", view["header.user-agent"])
+ self.assertEqual("cookievalue", view["cookie.sessionid"])
+ self.assertEqual("a", view["query.campaign"])
+ self.assertEqual("first", view["session.51D_one"])
+ self.assertEqual("dark", view["session.theme"])
+ self.assertEqual("203.0.113.5", view["server.client-ip"])
+ self.assertEqual("http", view["header.protocol"])
+ self.assertEqual("GET", view["server.request-method"])
+ self.assertEqual("/page", view["server.request-path"])
+ # The Cookie header carries every cookie, and cookies are their own
+ # part of the view.
+ self.assertNotIn("header.cookie", view)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/pipeline/fiftyone_pipeline_engines_fiftyone/src/fiftyone_pipeline_engines_fiftyone/share_usage.py b/pipeline/fiftyone_pipeline_engines_fiftyone/src/fiftyone_pipeline_engines_fiftyone/share_usage.py
index 210dfb0a..ec2a657a 100644
--- a/pipeline/fiftyone_pipeline_engines_fiftyone/src/fiftyone_pipeline_engines_fiftyone/share_usage.py
+++ b/pipeline/fiftyone_pipeline_engines_fiftyone/src/fiftyone_pipeline_engines_fiftyone/share_usage.py
@@ -29,12 +29,19 @@
from importlib.metadata import version
import requests
+from fiftyone_pipeline_core.basiclist_evidence_keyfilter import \
+ BasicListEvidenceKeyFilter
from fiftyone_pipeline_engines.engine import Engine
from .share_usage_evidencekeyfilter import ShareUsageEvidenceKeyFilter, \
ShareUsageTrackerEvidenceKeyFilter
from .share_usage_tracker import ShareUsageTracker
+# The key the web integration gives the web request itself. Written out
+# rather than imported, so the element still loads beside an older
+# fiftyone_pipeline_core.
+HTTP_REQUEST_KEY = "server.http-request"
+
# The maximum length of a piece of evidence's value which can be
# added to the usage data being sent.
SHARE_USAGE_MAX_EVIDENCE_LENGTH = 512
@@ -123,11 +130,13 @@ def __init__(
included = None if query_whitelist is None else \
list(query_whitelist) + ["session-id", "sequence"]
- # When sessions are tracked, the pipeline keeps the session cookie
- # for the tracker. The data sent leaves it out unless all evidence
+ # The element accepts no evidence, so that caching, the ETag and the
+ # Vary header never depend on it. It shares what it finds instead,
+ # and these filters decide which of that is shared and which tells
+ # a repeat apart. When sessions are tracked the tracker reads the
+ # session cookie, which the data sent leaves out unless all evidence
# is shared.
- self.evidence_key_filter = ShareUsageEvidenceKeyFilter(
- cookie, included, header_blacklist, track_session, share_all)
+ self.evidence_key_filter = BasicListEvidenceKeyFilter([])
self.data_evidence_key_filter = ShareUsageEvidenceKeyFilter(
cookie, included, header_blacklist, False, share_all)
self.tracker_evidence_key_filter = \
@@ -214,14 +223,40 @@ def get_evidence_key_filter(self):
"""!
- The share useage element comes with its own evidence
- key filter that uses the whitelists and blacklists to
- determine which evidence to share
-
+ The usage sharing element accepts no evidence. See found_evidence
+ for what it shares.
"""
return self.evidence_key_filter
+ def found_evidence(self, flow_data):
+
+ """!
+ The evidence the element finds to share. These are the values of the
+ web request, when the web integration added it as
+ server.http-request, and every evidence value in the flow data,
+ whether an element accepted it or not. A key found in both places
+ takes the evidence value. The web request itself is never one of
+ them.
+
+ @type flow_data: FlowData
+ @param flow_data: The flow data being processed
+ @rtype: dict
+ @return: The evidence found, before the sharing rules apply
+ """
+
+ evidence = flow_data.evidence
+ # An older fiftyone_pipeline_core sets nothing aside.
+ unaccepted = getattr(evidence, "get_unaccepted", dict)()
+ web_request = unaccepted.get(HTTP_REQUEST_KEY) or \
+ evidence.get(HTTP_REQUEST_KEY) or {}
+ found = {}
+ for source in (web_request, unaccepted, evidence.get_all()):
+ for key, value in source.items():
+ if str(key).lower() != HTTP_REQUEST_KEY:
+ found[key] = value
+ return found
+
def share_send_usage(self):
"""!
Internal method to send the share usage bundle to the 51Degrees servers
@@ -319,8 +354,11 @@ def process_internal(self, flow_data):
"""
if random.uniform(0, 1) <= self.share_percentage:
- cachekey = self.tracker_evidence_key_filter.filter_evidence(flow_data.evidence.get_all())
- cachekey = json.dumps(cachekey)
+ # A hash of the values that would be shared, as the element
+ # accepts no evidence of its own.
+ cachekey = self.tracker_evidence_key_filter.filter_evidence(
+ self.found_evidence(flow_data))
+ cachekey = json.dumps(cachekey, sort_keys=True, default=str)
share = self.tracker.track(cachekey)
if share:
self.tracker.set_cache_value(cachekey)
@@ -336,7 +374,7 @@ def get_data_from_evidence(self, flow_data):
"""
data = ShareUsageData()
- for key, value in flow_data.evidence.get_all().items():
+ for key, value in self.found_evidence(flow_data).items():
if key == 'server.client-ip':
# The client IP is dealt with separately for backwards
# compatibility purposes.
diff --git a/pipeline/fiftyone_pipeline_engines_fiftyone/src/fiftyone_pipeline_engines_fiftyone/share_usage_evidencekeyfilter.py b/pipeline/fiftyone_pipeline_engines_fiftyone/src/fiftyone_pipeline_engines_fiftyone/share_usage_evidencekeyfilter.py
index 5f2292f3..dc4d4264 100644
--- a/pipeline/fiftyone_pipeline_engines_fiftyone/src/fiftyone_pipeline_engines_fiftyone/share_usage_evidencekeyfilter.py
+++ b/pipeline/fiftyone_pipeline_engines_fiftyone/src/fiftyone_pipeline_engines_fiftyone/share_usage_evidencekeyfilter.py
@@ -26,16 +26,15 @@
# this are the ones 51Degrees sets, and are shared.
FIFTYONE_PREFIX = "51d_"
-# The Cookie header carries every cookie, including the ones the cookie rule
-# leaves out, so it is not shared unless all evidence is.
-BLOCKED_HEADERS = ("cookie",)
-
# Evidence that is never shared, whatever the settings, matched as the whole
# key or after a '.'. 'id.email' and 'id.salt' are a raw email address and the
# salt used to make hashed email identifiers. 'request-query' is the whole query
# string, which would undo the rules for its parameters. 'request-path' would
# make each page a visitor opens look like a new session to the tracker.
-NEVER_SHARED_KEYS = ("id.email", "id.salt", "request-query", "request-path")
+# 'header.cookie' is the Cookie header, which carries every cookie, and the
+# cookies that are shared are shared as their own 'cookie.' evidence.
+NEVER_SHARED_KEYS = (
+ "id.email", "id.salt", "request-query", "request-path", "header.cookie")
class ShareUsageEvidenceKeyFilter(BasicListEvidenceKeyFilter):
"""
@@ -110,8 +109,7 @@ def filter_evidence_key(self, key):
elif prefix == "header":
# Add the header to the list if the header name does not
# appear in the list of blocked headers
- return field not in self.header_blacklist and \
- field not in BLOCKED_HEADERS
+ return field not in self.header_blacklist
elif prefix == "cookie":
# Only add cookies that start with the 51Degrees cookie
# prefix
diff --git a/pipeline/fiftyone_pipeline_engines_fiftyone/tests/test_share_usage_accepts_no_evidence.py b/pipeline/fiftyone_pipeline_engines_fiftyone/tests/test_share_usage_accepts_no_evidence.py
new file mode 100644
index 00000000..e683f856
--- /dev/null
+++ b/pipeline/fiftyone_pipeline_engines_fiftyone/tests/test_share_usage_accepts_no_evidence.py
@@ -0,0 +1,208 @@
+# *********************************************************************
+# This Original Work is copyright of 51 Degrees Mobile Experts Limited.
+# Copyright 2026 51 Degrees Mobile Experts Limited, Davidson House,
+# Forbury Square, Reading, Berkshire, United Kingdom RG1 3EU.
+#
+# This Original Work is licensed under the European Union Public Licence
+# (EUPL) v.1.2 and is subject to its terms as set out below.
+#
+# If a copy of the EUPL was not distributed with this file, You can obtain
+# one at https://opensource.org/licenses/EUPL-1.2.
+#
+# The 'Compatible Licences' set out in the Appendix to the EUPL (as may be
+# amended by the European Commission) shall be deemed incompatible for
+# the purposes of the Work and the provisions of the compatibility
+# clause in Article 5 of the EUPL shall not apply.
+#
+# If using the Work as, or as part of, a network application, by
+# including the attribution notice(s) required under Article 5 of the EUPL
+# in the end user terms of the application under an appropriate heading,
+# such notice(s) shall fulfill the requirements of that article.
+# *********************************************************************
+
+"""The usage sharing element accepts no evidence, so a pipeline's accepted
+evidence, and the cache keys, ETag and Vary header built from it, never
+depend on it. It shares what it finds in the flow data and in the web
+request instead, by the same rules as before."""
+
+import unittest
+
+from flask import Flask, request
+
+from fiftyone_pipeline_core.basiclist_evidence_keyfilter import \
+ BasicListEvidenceKeyFilter
+from fiftyone_pipeline_core.flowelement import FlowElement
+from fiftyone_pipeline_core.pipelinebuilder import PipelineBuilder
+from fiftyone_pipeline_core.web import webevidence
+from fiftyone_pipeline_engines_fiftyone.share_usage import ShareUsage
+
+# No JavaScript or set header elements, so the filters are those of the
+# usage sharing element and of UserAgentElement alone.
+NO_DEFAULT_ELEMENTS = {
+ "add_javascript_builder": False,
+ "use_setheader_properties": False,
+}
+
+CHROME = ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
+ "(KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36")
+
+
+class UserAgentElement(FlowElement):
+ """A stand-in for an engine, accepting the User-Agent alone."""
+
+ def __init__(self):
+ super(UserAgentElement, self).__init__()
+ self.datakey = "useragent"
+
+ def process_internal(self, flowdata):
+ pass
+
+ def get_evidence_key_filter(self):
+ return BasicListEvidenceKeyFilter(["header.user-agent"])
+
+
+def usage_element(**settings):
+ """A usage sharing element that keeps what it would share, because its
+ package size is never reached, and sends nothing."""
+
+ return ShareUsage(
+ requested_package_size=1000, endpoint="http://127.0.0.1:9",
+ **settings)
+
+
+def pipeline_with(usage):
+ return PipelineBuilder(NO_DEFAULT_ELEMENTS) \
+ .add(UserAgentElement()).add(usage).build()
+
+
+def request_evidence(headers, path="/"):
+ """The evidence the web integration makes from a request."""
+
+ with Flask(__name__).test_request_context(path, headers=headers):
+ return webevidence(request)
+
+
+class ShareUsageAcceptsNoEvidenceTests(unittest.TestCase):
+
+ def test_the_pipeline_accepts_no_header_for_usage_sharing(self):
+ usage = usage_element()
+ pipeline = pipeline_with(usage)
+
+ for key in ("header.if-none-match", "header.x-test",
+ "cookie.51d_screenpixelswidth", "server.client-ip"):
+ self.assertFalse(usage.get_evidence_key_filter()
+ .filter_evidence_key(key), key)
+ self.assertFalse(any(
+ element.filter_evidence_key(key)
+ for element in pipeline.flow_elements), key)
+ self.assertTrue(any(
+ element.filter_evidence_key("header.user-agent")
+ for element in pipeline.flow_elements))
+
+ def test_a_revalidation_has_the_same_accepted_evidence(self):
+ """The ETag of a client-side endpoint hashes the evidence the
+ pipeline accepts, so a revalidation, which adds If-None-Match, has
+ to leave that evidence as it was."""
+
+ pipeline = pipeline_with(usage_element())
+ accepted = []
+ for headers in ({"User-Agent": CHROME},
+ {"User-Agent": CHROME, "If-None-Match": '"a-tag"'}):
+ flowdata = pipeline.create_flowdata()
+ flowdata.evidence.add_from_dict(request_evidence(headers))
+ flowdata.process()
+ accepted.append(dict(flowdata.evidence.get_all()))
+
+ self.assertEqual(accepted[0], accepted[1])
+ self.assertNotIn("header.if-none-match", accepted[1])
+
+ def test_the_web_request_is_shared_by_the_same_rules(self):
+ usage = usage_element(header_blacklist=["x-blocked"])
+ flowdata = pipeline_with(usage).create_flowdata()
+ flowdata.evidence.add_from_dict(request_evidence({
+ "User-Agent": CHROME,
+ "X-Test": "only usage sharing reads this",
+ "X-Blocked": "never shared",
+ "Cookie": "51D_ScreenPixelsWidth=1920; other=private",
+ }, "/?51D_GetHighEntropyValues=abc&password=secret"))
+ flowdata.process()
+
+ shared = "".join(usage.share_data)
+ self.assertIn(
+ 'only usage sharing reads this',
+ shared)
+ self.assertIn('', shared)
+ self.assertIn(
+ '1920', shared)
+ self.assertIn(
+ 'abc', shared)
+ for left_out in ('Name="x-blocked"', 'Name="cookie"', "private",
+ "secret", "http-request"):
+ self.assertNotIn(left_out, shared)
+
+ def test_the_method_is_shared_and_the_path_and_cookie_header_are_not(self):
+ """Even when all evidence is shared."""
+
+ usage = usage_element(share_all=True)
+ flowdata = pipeline_with(usage).create_flowdata()
+ flowdata.evidence.add_from_dict(request_evidence({
+ "User-Agent": CHROME,
+ "Cookie": "sessionid=cookievalue",
+ }, "/page?a=1"))
+ flowdata.process()
+
+ shared = "".join(usage.share_data)
+ self.assertIn("request-method", shared)
+ self.assertIn('cookievalue',
+ shared)
+ self.assertNotIn("/page", shared)
+ self.assertNotIn('', shared)
+
+ def test_a_key_in_both_places_is_shared_once_with_the_evidence_value(self):
+ usage = usage_element()
+ flowdata = pipeline_with(usage).create_flowdata()
+ flowdata.evidence.add_from_dict(
+ request_evidence({"X-Test": "from the request"}))
+ flowdata.evidence.add("header.x-test", "from the evidence")
+ flowdata.process()
+
+ shared = "".join(usage.share_data)
+ self.assertEqual(1, shared.count('Name="x-test"'))
+ self.assertIn(
+ '', shared)
+
+ def test_evidence_added_by_hand_is_shared_as_before(self):
+ usage = usage_element()
+ flowdata = pipeline_with(usage).create_flowdata()
+ flowdata.evidence.add("header.user-agent", CHROME)
+ flowdata.evidence.add("header.x-test", "by hand")
+ flowdata.evidence.add("cookie.51D_ScreenPixelsWidth", "1920")
+ flowdata.evidence.add("cookie.other", "private")
+ flowdata.evidence.add("server.client-ip", "203.0.113.42")
+ flowdata.process()
+
+ shared = "".join(usage.share_data)
+ self.assertIn('', shared)
+ self.assertIn(
+ '1920', shared)
+ self.assertIn("203.0.113.42", shared)
+ self.assertNotIn("private", shared)
+
+ def test_a_repeat_is_told_apart_by_what_would_be_shared(self):
+ """The tracker's hash is of the values that would be shared, so a
+ header no other element accepts still tells two requests apart,
+ and a repeat of the same request is shared once."""
+
+ usage = usage_element()
+ pipeline = pipeline_with(usage)
+ for value in ("first", "first", "second"):
+ flowdata = pipeline.create_flowdata()
+ flowdata.evidence.add_from_dict(
+ request_evidence({"User-Agent": CHROME, "X-Test": value}))
+ flowdata.process()
+
+ self.assertEqual(2, len(usage.share_data))
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/pipeline/fiftyone_pipeline_engines_fiftyone/tests/test_share_usage_evidencekeyfilter.py b/pipeline/fiftyone_pipeline_engines_fiftyone/tests/test_share_usage_evidencekeyfilter.py
index 16184d52..03ab1380 100644
--- a/pipeline/fiftyone_pipeline_engines_fiftyone/tests/test_share_usage_evidencekeyfilter.py
+++ b/pipeline/fiftyone_pipeline_engines_fiftyone/tests/test_share_usage_evidencekeyfilter.py
@@ -112,7 +112,7 @@ def test_share_all(self):
keys = ShareUsageEvidenceKeyFilter(share_all=True)
- self.assertTrue(keys.filter_evidence_key("header.cookie"))
+ self.assertFalse(keys.filter_evidence_key("header.cookie"))
self.assertTrue(keys.filter_evidence_key("cookie.sessionid"))
self.assertTrue(keys.filter_evidence_key("query.password"))
self.assertTrue(keys.filter_evidence_key("query.id.usage"))
@@ -127,7 +127,8 @@ def test_share_all(self):
def test_never_shared_in_every_mode(self):
"""!
Check that the email address, the salt for hashed email
- identifiers, the query string and the path are never shared,
+ identifiers, the query string, the path and the Cookie header are never
+ shared,
whether bare or after a prefix, and without regard to case.
"""
@@ -140,7 +141,8 @@ def test_never_shared_in_every_mode(self):
"query.id.email", "QUERY.ID.EMAIL",
"header.id.salt", "HEADER.ID.SALT",
"server.request-query", "SERVER.REQUEST-QUERY",
- "server.request-path", "SERVER.REQUEST-PATH"):
+ "server.request-path", "SERVER.REQUEST-PATH",
+ "header.cookie", "HEADER.COOKIE"):
self.assertFalse(keys.filter_evidence_key(key), key)
def test_never_shared_keys_match_whole_parts(self):
diff --git a/pipeline/fiftyone_pipeline_engines_fiftyone/tests/test_usage.py b/pipeline/fiftyone_pipeline_engines_fiftyone/tests/test_usage.py
index 4f1bea40..d501af16 100644
--- a/pipeline/fiftyone_pipeline_engines_fiftyone/tests/test_usage.py
+++ b/pipeline/fiftyone_pipeline_engines_fiftyone/tests/test_usage.py
@@ -574,9 +574,9 @@ def test_empty_query_whitelist(self):
def test_session_cookie(self):
"""!
- Check that with session tracking on the session cookie is kept in
- the evidence, but neither it nor any other ordinary cookie is
- shared.
+ Check that with session tracking on the tracker reads the session
+ cookie, which the element does not accept as evidence, and that
+ neither it nor any other ordinary cookie is shared.
"""
usage = ShareUsage(
@@ -595,7 +595,8 @@ def test_session_cookie(self):
flowdata.process()
self.waitForUsageThreads()
- self.assertTrue(usage.get_evidence_key_filter().filter_evidence_key("cookie.sessionid"))
+ self.assertFalse(usage.get_evidence_key_filter().filter_evidence_key("cookie.sessionid"))
+ self.assertTrue(usage.tracker_evidence_key_filter.filter_evidence_key("cookie.sessionid"))
self.assertEqual(len(app.received), 1)
self.assertIn("some user agent", app.received[0])
self.assertNotIn("session value", app.received[0])