Skip to content

Add configurable legal links (Impressum, Privacy, Terms)#396

Merged
t0mdavid-m merged 4 commits into
mainfrom
claude/laughing-fermat-ppiw0s
Jun 17, 2026
Merged

Add configurable legal links (Impressum, Privacy, Terms)#396
t0mdavid-m merged 4 commits into
mainfrom
claude/laughing-fermat-ppiw0s

Conversation

@t0mdavid-m

@t0mdavid-m t0mdavid-m commented Jun 17, 2026

Copy link
Copy Markdown
Member

Summary

Adds support for configurable legal page URLs (Impressum, Privacy Policy, Terms of Use) that appear in the sidebar footer on every page and in the GDPR consent banner. Self-hosted forks can now override these links via settings.json while maintaining sensible OpenMS defaults for apps built from older configurations.

Key Changes

  • New get_legal_links() function in src/common/common.py that merges optional legal_links overrides from settings.json with built-in OpenMS defaults, ensuring empty/blank overrides don't erase defaults
  • DEFAULT_LEGAL_LINKS constant pointing to official OpenMS pages (https://openms.de/impressum, /privacy, /terms), allowing downstream apps without a legal_links key to inherit working links by default
  • Sidebar footer rendering in page_setup() displays the three legal links with proper styling and target="_blank" behavior
  • GDPR consent banner integration — the privacy policy URL from get_legal_links() is now passed to the consent component via captcha_control(privacy_policy_url=...), keeping consent and policy in sync
  • TypeScript consent banner update (gdpr_consent/src/main.ts) to accept and wire the privacy policy URL into Klaro's configuration
  • Documentation in README.md explaining the override mechanism and example settings.json configuration
  • Comprehensive unit tests (tests/test_legal_links.py) covering defaults, missing settings, full overrides, partial overrides, and empty/None value handling, with proper mocking to avoid Streamlit runtime dependencies

Implementation Details

  • The merge strategy uses {**DEFAULT_LEGAL_LINKS, **{k: v for k, v in overrides.items() if v}} to ensure falsy override values (empty strings, None) fall back to defaults rather than erasing them
  • Tests use a FakeSessionState class to mock Streamlit's session state without requiring a running app context, mirroring the pattern in tests/test_parameter_presets.py
  • The settings.json example includes the full default configuration for clarity, though the code gracefully handles missing or partial legal_links objects

https://claude.ai/code/session_01WCamMNCunp9T2aScEKKUvx

Summary by CodeRabbit

  • New Features

    • Added legal pages (Impressum, Privacy Policy, Terms of Use) to the app sidebar.
    • The GDPR consent banner now links directly to the configured privacy policy URL.
    • Added centrally maintained default legal links, with configuration-based overrides for self-hosted deployments.
  • Documentation

    • Documented how to configure and override legal links, including Impressum operator naming and how the GDPR banner derives its privacy URL.

claude added 2 commits June 16, 2026 17:04
Every page now shows Impressum, Privacy Policy and Terms of Use links in
the sidebar footer, and the GDPR consent banner links to the privacy
policy so consent and policy are tied together. Links default to the
official OpenMS pages and are overridable via a new "legal_links" object
in settings.json, so self-hosting forks can point them at their own legal
pages (an Impressum must name the actual operator).

- settings.json: add legal_links (impressum/privacy/terms) defaults
- common.py: add get_legal_links() (settings overrides merged over the
  hardcoded OpenMS defaults, so older forks still inherit links) and
  render the three links in the sidebar on every page
- captcha_.py: forward the privacy policy URL to the consent component
- gdpr_consent: set Klaro privacyPolicyUrl so the banner shows a working
  privacy-policy link; rebuild dist/bundle.js
- README: document the legal_links override
- tests: cover get_legal_links default/override/fallback behavior

https://claude.ai/code/session_01WCamMNCunp9T2aScEKKUvx
The module-level streamlit/dependency mocks were left in sys.modules after
import, leaking into later-collected test modules and breaking the
AppTest-based tests (test_run_subprocess, test_simple_workflow) with
"ModuleNotFoundError: No module named 'streamlit.testing'; 'streamlit' is
not a package".

Save and restore the mocked sys.modules entries and drop the cached
src.common.common module after import, mirroring the established pattern in
tests/test_parameter_presets.py. Full suite now passes (39 passed,
2 skipped).

https://claude.ai/code/session_01WCamMNCunp9T2aScEKKUvx
@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2c190dea-2e97-480f-8d99-dccbd145062d

📥 Commits

Reviewing files that changed from the base of the PR and between 6c46bc2 and 59b803d.

📒 Files selected for processing (1)
  • src/common/common.py

📝 Walkthrough

Walkthrough

Adds configurable legal page links (Impressum, Privacy Policy, Terms of Use) to the sidebar. A new get_legal_links() helper merges built-in OpenMS default URLs with settings.json overrides. The privacy URL is threaded through captcha_control() into the Klaro GDPR consent banner via a new translations field.

Changes

Legal Links Feature

Layer / File(s) Summary
Default legal links and merge helper
settings.json, src/common/common.py
Introduces DEFAULT_LEGAL_LINKS constant with OpenMS centrally-maintained Impressum, Privacy, and Terms URLs, and implements get_legal_links() to merge built-in defaults with non-empty session_state.settings["legal_links"] overrides. Configuration block added to settings.json.
Captcha control privacy URL parameter
src/common/captcha_.py
Extends captcha_control() signature with optional privacy_policy_url parameter, updates docstring, and forwards it to the gdpr_consent component invocation.
Klaro GDPR banner privacy URL support
gdpr_consent/src/main.ts
Extends klaroConfig TypeScript type with optional translations field and conditionally assigns klaroConfig.translations.zz.privacyPolicyUrl from the privacy_policy render argument.
Captcha call sites and sidebar rendering
src/common/common.py
Updates both captcha invocation sites to pass privacy_policy_url from get_legal_links()["privacy"] and renders Impressum, Privacy Policy, and Terms HTML links in the sidebar using get_legal_links().
Unit tests for get_legal_links()
tests/test_legal_links.py
Comprehensive test module validating default URLs, empty/missing settings fallback, full and partial overrides, and blank/None value handling. Uses mocked Streamlit imports and FakeSessionState test double to isolate the helper from runtime dependencies.
README documentation
README.md
Documents default OpenMS legal page destinations, sidebar link behavior, privacy URL reuse in GDPR consent banner, and instructs users how to override links via settings.json legal_links configuration.

🐇 A sidebar now gleams with legal links three,
Impressum and Privacy for all to see!
The consent banner follows the URL you set,
No GDPR surprises—no cause for regret.
Hop along, fork and override with glee! 🌿

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 77.78% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add configurable legal links (Impressum, Privacy, Terms)' accurately and concisely summarizes the primary change: adding configurable legal page links with specific control over Impressum, Privacy, and Terms URLs.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/laughing-fermat-ppiw0s

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src/common/common.py (2)

798-810: 💤 Low value

Consider sanitizing URLs before HTML injection for defense in depth.

The legal link URLs from get_legal_links() are directly interpolated into HTML using f-strings without sanitization or escaping (lines 805-807). While settings.json is a trusted configuration file and not user input, sanitizing URLs would add defense in depth against accidental or malicious configuration changes.

Consider using html.escape() for the URLs or validating them against an allowed pattern.

🛡️ Proposed fix to escape URLs
+import html
+
 # Legal links (Impressum, Privacy Policy, Terms of Use), shown on every
 # page. URLs are configurable via "legal_links" in settings.json.
 links = get_legal_links()
 st.markdown(
     '<div style="text-align:center; font-size:0.8rem; '
     'margin-top:0.5rem; color:`#a4a5ad`;">'
-    f'<a href="{links["impressum"]}" target="_blank" rel="noopener">Impressum</a> &middot; '
-    f'<a href="{links["privacy"]}" target="_blank" rel="noopener">Privacy Policy</a> &middot; '
-    f'<a href="{links["terms"]}" target="_blank" rel="noopener">Terms of Use</a>'
+    f'<a href="{html.escape(links["impressum"])}" target="_blank" rel="noopener">Impressum</a> &middot; '
+    f'<a href="{html.escape(links["privacy"])}" target="_blank" rel="noopener">Privacy Policy</a> &middot; '
+    f'<a href="{html.escape(links["terms"])}" target="_blank" rel="noopener">Terms of Use</a>'
     "</div>",
     unsafe_allow_html=True,
 )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/common/common.py` around lines 798 - 810, The URLs obtained from
get_legal_links() are directly interpolated into HTML markup without escaping,
creating a potential security risk if the configuration file is ever
compromised. Wrap each URL reference (links["impressum"], links["privacy"], and
links["terms"]) with html.escape() before including them in the f-strings to
sanitize the URLs and prevent HTML injection attacks even when using
unsafe_allow_html=True with st.markdown().

553-566: Remove the unconditional captcha_control() call at line 553.

The function captcha_control() is called unconditionally at line 553 and again conditionally at line 566 with identical arguments. Since captcha_control() internally guards its logic with the same condition (if "controllo" not in st.session_state or st.session_state["controllo"] == False), the first unconditional call is redundant. The function will either display the CAPTCHA/consent flow on both calls or skip its main logic on both—making the unconditional call unnecessary. Removing line 553 and the blank line after it simplifies the flow without changing behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/common/common.py` around lines 553 - 566, Remove the redundant
unconditional call to
captcha_control(privacy_policy_url=get_legal_links()["privacy"]) that appears
before the conditional block. Since the same captcha_control function is called
again immediately after within an if statement that checks for "controllo" not
in st.session_state or the condition on params["controllo"], and since
captcha_control internally guards its logic with the same condition, the first
unconditional call is duplicate and should be deleted along with any associated
blank lines.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/common/common.py`:
- Around line 798-810: The URLs obtained from get_legal_links() are directly
interpolated into HTML markup without escaping, creating a potential security
risk if the configuration file is ever compromised. Wrap each URL reference
(links["impressum"], links["privacy"], and links["terms"]) with html.escape()
before including them in the f-strings to sanitize the URLs and prevent HTML
injection attacks even when using unsafe_allow_html=True with st.markdown().
- Around line 553-566: Remove the redundant unconditional call to
captcha_control(privacy_policy_url=get_legal_links()["privacy"]) that appears
before the conditional block. Since the same captcha_control function is called
again immediately after within an if statement that checks for "controllo" not
in st.session_state or the condition on params["controllo"], and since
captcha_control internally guards its logic with the same condition, the first
unconditional call is duplicate and should be deleted along with any associated
blank lines.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 761755f5-ba6e-4949-af99-6d1cfe4c14c1

📥 Commits

Reviewing files that changed from the base of the PR and between e9d162a and f5d2953.

⛔ Files ignored due to path filters (1)
  • gdpr_consent/dist/bundle.js is excluded by !**/dist/**
📒 Files selected for processing (6)
  • README.md
  • gdpr_consent/src/main.ts
  • settings.json
  • src/common/captcha_.py
  • src/common/common.py
  • tests/test_legal_links.py

claude added 2 commits June 17, 2026 11:33
The streamlit mock was installed into sys.modules, but src.common.common
was only popped AFTER importing get_legal_links. When test_gui.py runs
first (CI order: `pytest test_gui.py tests/`), it imports the real,
streamlit-bound common module into sys.modules; the subsequent mock-based
import here was then a cache hit returning the real-streamlit-bound
function, so the tests' session_state overrides had no effect and
get_legal_links returned the OpenMS defaults. The two override tests
failed in CI (they passed locally only when collected first).

Pop src.common.common BEFORE the import to force a fresh import under the
mock, then restore the original module afterward so the AppTest-based test
modules still get the genuine package. Full suite: 76 passed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WCamMNCunp9T2aScEKKUvx
In the narrow sidebar, the footer links broke at the space inside a label
(e.g. "Terms of Use" split into "Terms" / "of Use"). Add white-space:nowrap
to each link so labels stay intact; line breaks now happen only between links.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WCamMNCunp9T2aScEKKUvx
@t0mdavid-m t0mdavid-m merged commit a703e05 into main Jun 17, 2026
9 of 10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants