diff --git a/lib/galaxy/config/__init__.py b/lib/galaxy/config/__init__.py index 7ca7a1f15a7..673e5f26bc9 100644 --- a/lib/galaxy/config/__init__.py +++ b/lib/galaxy/config/__init__.py @@ -30,6 +30,7 @@ from typing import ( TypeVar, Union, ) +from urllib.parse import urlparse import yaml @@ -135,6 +136,7 @@ LOGGING_CONFIG_DEFAULT: Dict[str, Any] = { """Default value for logging configuration, passed to :func:`logging.config.dictConfig`""" VERSION_JSON_FILE = "version.json" +DEFAULT_EMAIL_FROM_LOCAL_PART = "galaxy-no-reply" def configure_logging(config, facts=None): @@ -612,16 +614,6 @@ class CommonConfigurationMixin: class GalaxyAppConfiguration(BaseAppConfiguration, CommonConfigurationMixin): - deprecated_options = ( - "database_file", - "track_jobs_in_database", - "blacklist_file", - "whitelist_file", - "sanitize_whitelist_file", - "user_library_import_symlink_whitelist", - "fetch_url_whitelist", - "containers_resolvers_config_file", - ) renamed_options = { "blacklist_file": "email_domain_blocklist_file", "whitelist_file": "email_domain_allowlist_file", @@ -629,7 +621,14 @@ class GalaxyAppConfiguration(BaseAppConfiguration, CommonConfigurationMixin): "user_library_import_symlink_whitelist": "user_library_import_symlink_allowlist", "fetch_url_whitelist": "fetch_url_allowlist", "containers_resolvers_config_file": "container_resolvers_config_file", + "activation_email": "email_from", } + + deprecated_options = list(renamed_options.keys()) + [ + "database_file", + "track_jobs_in_database", + ] + default_config_file_name = "galaxy.yml" deprecated_dirs = {"config_dir": "config", "data_dir": "database"} @@ -895,8 +894,6 @@ class GalaxyAppConfiguration(BaseAppConfiguration, CommonConfigurationMixin): self.nodejs_path = kwargs.get("nodejs_path") self.container_image_cache_path = self._in_data_dir(kwargs.get("container_image_cache_path", "container_cache")) self.output_size_limit = int(kwargs.get("output_size_limit", 0)) - # activation_email was used until release_15.03 - self.email_from = self.email_from or kwargs.get("activation_email") self.email_domain_blocklist_content = ( self._load_list_from_file(self._in_config_dir(self.email_domain_blocklist_file)) @@ -1046,7 +1043,7 @@ class GalaxyAppConfiguration(BaseAppConfiguration, CommonConfigurationMixin): if section.startswith("server:"): self.server_names.append(section.replace("server:", "", 1)) - self._set_galaxy_infrastructure_url(kwargs) + self._set_host_related_options(kwargs) # Asynchronous execution process pools - limited functionality for now, attach_to_pools is designed to allow # webless Galaxy server processes to attach to arbitrary message queues (e.g. as job handlers) so they do not @@ -1209,6 +1206,12 @@ class GalaxyAppConfiguration(BaseAppConfiguration, CommonConfigurationMixin): with open(filepath) as f: return [line.strip() for line in f] + def _set_host_related_options(self, kwargs): + # The following 3 method calls must be made in sequence + self._set_galaxy_infrastructure_url(kwargs) + self._set_hostname() + self._set_email_from() + def _set_galaxy_infrastructure_url(self, kwargs): # indicate if this was not set explicitly, so dependending on the context a better default # can be used (request url in a web thread, Docker parent in IE stuff, etc.) @@ -1227,6 +1230,16 @@ class GalaxyAppConfiguration(BaseAppConfiguration, CommonConfigurationMixin): if "UWSGI_PORT" in self.galaxy_infrastructure_url: raise Exception("UWSGI_PORT is not supported anymore") + def _set_hostname(self): + if self.galaxy_infrastructure_url_set: + self.hostname = urlparse(self.galaxy_infrastructure_url).hostname + else: + self.hostname = socket.getfqdn() + + def _set_email_from(self): + if not self.email_from: + self.email_from = f"{DEFAULT_EMAIL_FROM_LOCAL_PART}@{self.hostname}" + def reload_sanitize_allowlist(self, explicit=True): self.sanitize_allowlist = [] if not os.path.exists(self.sanitize_allowlist_file): diff --git a/lib/galaxy/job_execution/actions/post.py b/lib/galaxy/job_execution/actions/post.py index 62d168ce6b5..a198044fb86 100644 --- a/lib/galaxy/job_execution/actions/post.py +++ b/lib/galaxy/job_execution/actions/post.py @@ -3,7 +3,6 @@ Actions to be run at job completion (or output hda creation, as in the case of immediate_actions listed below. """ import datetime -import socket from markupsafe import escape @@ -53,7 +52,6 @@ class EmailAction(DefaultJobAction): @classmethod def execute(cls, app, sa_session, action, job, replacement_dict, final_job_state=None): try: - frm = app.config.email_from history_id_encoded = app.security.encode_id(job.history_id) link_invocation = None if job.workflow_invocation_step: @@ -62,19 +60,13 @@ class EmailAction(DefaultJobAction): f"{app.config.galaxy_infrastructure_url}/workflows/invocations/report?id={invocation_id_encoded}" ) link = f"{app.config.galaxy_infrastructure_url}/histories/view?id={history_id_encoded}" - if frm is None: - if action.action_arguments and "host" in action.action_arguments: - host = action.action_arguments["host"] - else: - host = socket.getfqdn() - frm = f"galaxy-no-reply@{host}" to = job.get_user_email() subject = f"Galaxy job completion notification from history '{job.history.name}'" outdata = ",\n".join(ds.dataset.display_name() for ds in job.output_datasets) body = f"Your Galaxy job generating dataset(s):\n\n{outdata}\n\nis complete as of {datetime.datetime.now().strftime('%I:%M')}. Click the link below to access your data: \n{link}" if link_invocation: body += f"\n\nWorkflow Invocation Report:\n{link_invocation}" - send_mail(frm, to, subject, body, app.config) + send_mail(app.config.email_from, to, subject, body, app.config) except Exception as e: log.error("EmailAction PJA Failed, exception: %s", unicodify(e)) diff --git a/lib/galaxy/managers/users.py b/lib/galaxy/managers/users.py index 622a91da2de..0190386099f 100644 --- a/lib/galaxy/managers/users.py +++ b/lib/galaxy/managers/users.py @@ -5,7 +5,6 @@ import hashlib import logging import random import re -import socket import time from datetime import datetime from typing import Optional @@ -484,7 +483,6 @@ class UserManager(base.ModelManager, deletable.PurgableManagerMixin): activation_link = url_for( controller="user", action="activate", activation_token=activation_token, email=escape(email), qualified=True ) - host = self.__get_host(trans) template_context = { "name": escape(username), "user_email": escape(email), @@ -500,10 +498,9 @@ class UserManager(base.ModelManager, deletable.PurgableManagerMixin): body = templates.render(TXT_ACTIVATION_EMAIL_TEMPLATE_RELPATH, template_context, self.app.config.templates_dir) html = templates.render(HTML_ACTIVATION_EMAIL_TEMPLATE_RELPATH, template_context, self.app.config.templates_dir) to = email - frm = self.app.config.email_from or f"galaxy-no-reply@{host}" subject = "Galaxy Account Activation" try: - util.send_mail(frm, to, subject, body, self.app.config, html=html) + util.send_mail(self.app.config.email_from, to, subject, body, self.app.config, html=html) return True except Exception: log.debug(body) @@ -536,18 +533,16 @@ class UserManager(base.ModelManager, deletable.PurgableManagerMixin): else: reset_user, prt = self.get_reset_token(trans, email) if prt: - host = self.__get_host(trans) reset_url = url_for(controller="login", action="start", token=prt.token) body = PASSWORD_RESET_TEMPLATE % ( - host, + trans.app.config.hostname, prt.expiration_time.strftime(trans.app.config.pretty_datetime_format), trans.request.host, reset_url, ) - frm = trans.app.config.email_from or f"galaxy-no-reply@{host}" subject = "Galaxy Password Reset" try: - util.send_mail(frm, email, subject, body, self.app.config) + util.send_mail(trans.app.config.email_from, email, subject, body, self.app.config) trans.sa_session.add(reset_user) trans.sa_session.flush() trans.log_event(f"User reset password: {email}") @@ -574,12 +569,6 @@ class UserManager(base.ModelManager, deletable.PurgableManagerMixin): return reset_user, prt return None, None - def __get_host(self, trans): - host = trans.request.host.split(":")[0] - if host in ["localhost", "127.0.0.1", "0.0.0.0"]: - host = socket.getfqdn() - return host - def send_subscription_email(self, email): if self.app.config.smtp_server is None: return "Subscribing to the mailing list has failed because mail is not configured for this Galaxy instance. Please contact your local Galaxy administrator." diff --git a/lib/galaxy/tools/errors.py b/lib/galaxy/tools/errors.py index c406a952c26..26aa0e017af 100644 --- a/lib/galaxy/tools/errors.py +++ b/lib/galaxy/tools/errors.py @@ -243,7 +243,6 @@ class EmailErrorReporter(ErrorReporter): to = self.app.config.error_email_to assert to, ValueError("Error reporting has been disabled for this Galaxy instance") - frm = self.app.config.email_from error_msg = validate_email_str(email) if not error_msg and self._can_access_dataset(user): to += f", {email.strip()}" @@ -255,4 +254,6 @@ class EmailErrorReporter(ErrorReporter): except Exception: pass - return util.send_mail(frm, to, subject, self.report, self.app.config, html=self.html_report) + return util.send_mail( + self.app.config.email_from, to, subject, self.report, self.app.config, html=self.html_report + ) diff --git a/test/unit/config/test_config_values.py b/test/unit/config/test_config_values.py index 43f34e13bea..b085b05b35b 100644 --- a/test/unit/config/test_config_values.py +++ b/test/unit/config/test_config_values.py @@ -3,6 +3,7 @@ import os import pytest from galaxy import config +from galaxy.config import DEFAULT_EMAIL_FROM_LOCAL_PART from galaxy.util.properties import running_from_source @@ -42,3 +43,10 @@ def test_base_config_if_running_not_from_source(monkeypatch): assert appconfig.config_dir == os.getcwd() assert appconfig.data_dir == os.path.join(appconfig.config_dir, "data") assert appconfig.managed_config_dir == os.path.join(appconfig.data_dir, "config") + + +def test_assign_email_from(monkeypatch): + appconfig = config.GalaxyAppConfiguration( + override_tempdir=False, galaxy_infrastructure_url="http://myhost:8080/galaxy/" + ) + assert appconfig.email_from == f"{DEFAULT_EMAIL_FROM_LOCAL_PART}@myhost"