diff --git a/README.md b/README.md index d5968e9..d093b28 100644 --- a/README.md +++ b/README.md @@ -242,7 +242,8 @@ The worker processing policy is documented in: ### 7. Test Helpdesk Mail Reset After importing a production database into the LAN test instance, reset all -Helpdesk-enabled projects to use the local Mailpit test mailbox with: +active projects to use the local Mailpit test mailbox for Helpdesk settings +with: - [reset_helpdesk_mail_settings.py](/home/iadnah/redmine/reset_helpdesk_mail_settings.py:1) diff --git a/docs/redmineup_local_fork_changelog.md b/docs/redmineup_local_fork_changelog.md index 7295fe8..6c432d2 100644 --- a/docs/redmineup_local_fork_changelog.md +++ b/docs/redmineup_local_fork_changelog.md @@ -32,6 +32,27 @@ environment. Before risky edits, archive the current plugin directories in - Run `validate_helpdesk_outbox_worker.py` after outbox or worker changes, then choose the external index target. +## 2026-04-25 - Test Helpdesk Credential Sanitization + +- Touched areas: + - Test instance post-import tooling + - Post-import validation +- Purpose: + - Ensure production database imports cannot leave real Helpdesk POP3/SMTP + credentials on projects where Helpdesk is disabled but settings rows still + exist. +- Behavior changed: + - `reset_helpdesk_mail_settings.py` now rewrites Helpdesk mail settings for + every active project, not only projects with `contacts_helpdesk` enabled. + - `validate_test_instance.py` checks every active project for Mailpit + Helpdesk settings. +- LAN test result: + - `./reset_helpdesk_mail_settings.py --dry-run` matched 52 active projects. + - `./reset_helpdesk_mail_settings.py` updated 1,092 setting rows across 52 + active projects. + - `./validate_test_instance.py` passed and reported 52 active projects with + matching Mailpit Helpdesk settings. + ## 2026-04-25 - Outbox Worker Processing Policy - Touched areas: diff --git a/docs/test_instance_post_import.md b/docs/test_instance_post_import.md index 004a409..5cc7797 100644 --- a/docs/test_instance_post_import.md +++ b/docs/test_instance_post_import.md @@ -69,8 +69,10 @@ Apply: ./reset_helpdesk_mail_settings.py ``` -This rewrites all active `contacts_helpdesk` projects to use Mailpit for POP3 -and SMTP. Passwords are written but not printed. +This rewrites all active projects to use Mailpit for Helpdesk POP3 and SMTP, +even if the Helpdesk module is currently disabled for a project. That prevents +imported real mail credentials from being used accidentally in the test +instance. Passwords are written but not printed. ## 4. Restart Passenger diff --git a/reset_helpdesk_mail_settings.py b/reset_helpdesk_mail_settings.py index ab782f6..4252348 100755 --- a/reset_helpdesk_mail_settings.py +++ b/reset_helpdesk_mail_settings.py @@ -2,8 +2,9 @@ """Reset RedmineUP Helpdesk mail settings on the LAN test Redmine instance. This is intended to be run after importing a production database into the test -instance. It finds projects with the Helpdesk module enabled and rewrites only -the incoming/outgoing mail settings so test mail flows through Mailpit. +instance. It rewrites every active project's incoming/outgoing Helpdesk mail +settings so test mail flows through Mailpit and imported real credentials cannot +be used accidentally. """ from __future__ import annotations @@ -113,7 +114,7 @@ class RemoteRedmine: def main() -> int: parser = argparse.ArgumentParser( - description="Reset Helpdesk mail settings for projects with the contacts_helpdesk module enabled." + description="Reset Helpdesk mail settings for all active projects." ) parser.add_argument("--ssh-host", default=os.getenv("REDMINE_SSH_HOST", DEFAULT_SSH_HOST)) parser.add_argument("--ssh-key", type=Path, default=Path(os.getenv("REDMINE_SSH_KEY", str(DEFAULT_SSH_KEY)))) @@ -141,12 +142,12 @@ def main() -> int: remote = RemoteRedmine(args.ssh_host, args.ssh_key, args.remote_redmine) try: - projects = find_helpdesk_projects(remote, args.project) + projects = find_active_projects(remote, args.project) if not projects: - print("No active projects with contacts_helpdesk enabled matched the requested filters.") + print("No active projects matched the requested filters.") return 0 - print(f"Matched {len(projects)} Helpdesk-enabled project(s):") + print(f"Matched {len(projects)} active project(s):") for project in projects: print(f" - #{project['id']} {project['identifier']} ({project['name']})") @@ -165,7 +166,7 @@ def main() -> int: return 1 -def find_helpdesk_projects(remote: RemoteRedmine, filters: list[str]) -> list[dict[str, Any]]: +def find_active_projects(remote: RemoteRedmine, filters: list[str]) -> list[dict[str, Any]]: where = ["p.status = 1"] if filters: clauses = [] @@ -183,9 +184,6 @@ SELECT HEX(CAST(JSON_OBJECT( 'name', p.name ) AS CHAR)) AS document FROM projects p -JOIN enabled_modules em - ON em.project_id = p.id - AND em.name = 'contacts_helpdesk' WHERE {' AND '.join(where)} ORDER BY p.identifier; """ diff --git a/validate_test_instance.py b/validate_test_instance.py index 8eb2b2c..559f592 100755 --- a/validate_test_instance.py +++ b/validate_test_instance.py @@ -231,7 +231,7 @@ ORDER BY identifier; if failures: results.append(CheckResult("FAIL", "Helpdesk Mailpit settings", "; ".join(failures[:8]))) else: - results.append(CheckResult("OK", "Helpdesk Mailpit settings", f"{len(settings_rows)} project(s) match")) + results.append(CheckResult("OK", "Helpdesk Mailpit settings", f"{len(settings_rows)} active project(s) match")) except Exception as exc: results.append(CheckResult("FAIL", "Database checks", f"{exc.__class__.__name__}: {exc}")) return results @@ -257,7 +257,6 @@ SELECT HEX(CAST(JSON_OBJECT( 'smtp_tls', MAX(CASE WHEN cs.name = 'helpdesk_smtp_tls' THEN cs.value END) ) AS CHAR)) AS document FROM projects p -JOIN enabled_modules em ON em.project_id = p.id AND em.name = 'contacts_helpdesk' LEFT JOIN contacts_settings cs ON cs.project_id = p.id WHERE p.status = 1 GROUP BY p.id, p.identifier