mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
Fix admin resend-activation email missing hostname
In a FastAPI request, UrlBuilder's NoMatchFound fallback called web.url_for without the popped qualified flag, and routes.url_for has no host available in an ASGI context. The admin resend-activation endpoint hits this path (since /user/activate is a WSGI-only @web.expose route) and produced links like http:///user/activate?... . Latent in the FastAPI endpoint since72d5cceda8("Add mail activation operation to api"); surfaced for users afterc9b849d934bumped fastapi 0.135.3 -> 0.136.0. Qualify the fallback URL with request.base_url when qualified=True. Integration test posts to /api/users/{user_id}/send_activation_email as admin with mock SMTP capture, then asserts the captured email body contains a fully-qualified activation link with a non-empty host. Would have caught the regression that produced http:///user/activate?... .
This commit is contained in:
@@ -228,10 +228,15 @@ class UrlBuilder:
|
||||
url = f"{url}?{urlencode(query_params)}"
|
||||
return url
|
||||
except NoMatchFound:
|
||||
# Fallback to legacy url_for
|
||||
# Fallback to legacy WSGI url_for for routes not registered with FastAPI
|
||||
if query_params:
|
||||
path_params.update(query_params)
|
||||
return web.url_for(name, **path_params)
|
||||
url = web.url_for(name, **path_params)
|
||||
if qualified and not url.startswith(("http://", "https://")):
|
||||
# routes.url_for has no thread-local request_config in an ASGI
|
||||
# request, so qualify the URL using the FastAPI request base_url.
|
||||
url = str(self.request.base_url).rstrip("/") + url
|
||||
return url
|
||||
|
||||
def _url_path_for(self, name: str, **path_params) -> str:
|
||||
"""O(1) route lookup using the app's pre-built name index.
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
from typing import (
|
||||
ClassVar,
|
||||
)
|
||||
@@ -91,3 +94,31 @@ class TestUnexposedUsersIntegration(UsersIntegrationCase):
|
||||
# And the current user has all fields, so no limited fields.
|
||||
expected_limited_user_keys = set()
|
||||
expected_regular_user_list_count = 1
|
||||
|
||||
|
||||
class TestAdminResendActivationEmail(integration_util.IntegrationTestCase):
|
||||
email_directory: ClassVar[str]
|
||||
|
||||
@classmethod
|
||||
def handle_galaxy_config_kwds(cls, config):
|
||||
super().handle_galaxy_config_kwds(config)
|
||||
cls.email_directory = cls._test_driver.mkdtemp()
|
||||
config["user_activation_on"] = True
|
||||
config["activation_grace_period"] = 3
|
||||
config["email_from"] = "galaxy-noreply@example.com"
|
||||
config["smtp_server"] = f"mock_emails_to_path://{cls.email_directory}/email.json"
|
||||
|
||||
def test_resend_activation_includes_qualified_link(self):
|
||||
user = self._setup_user("resend-activation@test.gx")
|
||||
response = self._post(f"users/{user['id']}/send_activation_email", admin=True)
|
||||
self._assert_status_code_is_ok(response)
|
||||
|
||||
with open(os.path.join(self.email_directory, "email.json")) as f:
|
||||
email = json.loads(f.read())
|
||||
assert email["to"] == "resend-activation@test.gx"
|
||||
assert email["subject"] == "Galaxy Account Activation"
|
||||
match = re.search(r"(https?://[^/\s]+/user/activate\?[^\s]+)", email["body"])
|
||||
assert match, f"No qualified activation link found in email body:\n{email['body']}"
|
||||
link = match.group(1)
|
||||
assert "activation_token=" in link
|
||||
assert "email=" in link
|
||||
|
||||
Reference in New Issue
Block a user