From b98ea48b52c3889475ccccbf2febc368ab9f32d0 Mon Sep 17 00:00:00 2001 From: John Davis Date: Fri, 10 Feb 2023 10:30:47 -0500 Subject: [PATCH 1/7] Add actiavation_email to renamed options Used prior to version 15.03; no need for special case --- lib/galaxy/config/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/galaxy/config/__init__.py b/lib/galaxy/config/__init__.py index 7ca7a1f15a7..7ba11cc0727 100644 --- a/lib/galaxy/config/__init__.py +++ b/lib/galaxy/config/__init__.py @@ -629,6 +629,7 @@ 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", } default_config_file_name = "galaxy.yml" deprecated_dirs = {"config_dir": "config", "data_dir": "database"} @@ -895,8 +896,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)) From 84a9130abcdb6f71f46b7296c2d084d50c54713b Mon Sep 17 00:00:00 2001 From: John Davis Date: Fri, 10 Feb 2023 11:20:35 -0500 Subject: [PATCH 2/7] Set app.config.email_from consistently --- lib/galaxy/config/__init__.py | 8 ++++++++ lib/galaxy/job_execution/actions/post.py | 10 +--------- lib/galaxy/managers/users.py | 7 ++----- lib/galaxy/tools/errors.py | 5 +++-- test/unit/config/test_config_values.py | 6 ++++++ 5 files changed, 20 insertions(+), 16 deletions(-) diff --git a/lib/galaxy/config/__init__.py b/lib/galaxy/config/__init__.py index 7ba11cc0727..d1995b2e952 100644 --- a/lib/galaxy/config/__init__.py +++ b/lib/galaxy/config/__init__.py @@ -135,6 +135,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): @@ -1046,6 +1047,8 @@ class GalaxyAppConfiguration(BaseAppConfiguration, CommonConfigurationMixin): self.server_names.append(section.replace("server:", "", 1)) self._set_galaxy_infrastructure_url(kwargs) + # _set_email_from() should be called AFTER _set_galaxy_infrastructure_url() + self._set_email_from() # 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 @@ -1226,6 +1229,11 @@ class GalaxyAppConfiguration(BaseAppConfiguration, CommonConfigurationMixin): if "UWSGI_PORT" in self.galaxy_infrastructure_url: raise Exception("UWSGI_PORT is not supported anymore") + def _set_email_from(self): + if not self.email_from: + hostname = self.galaxy_infrastructure_url or socket.getfqdn() + self.email_from = f"{DEFAULT_EMAIL_FROM_LOCAL_PART}@{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..811332ff924 100644 --- a/lib/galaxy/managers/users.py +++ b/lib/galaxy/managers/users.py @@ -484,7 +484,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 +499,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) @@ -544,10 +542,9 @@ class UserManager(base.ModelManager, deletable.PurgableManagerMixin): 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}") 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..bb23ddd494d 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,8 @@ 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="myhost") + assert appconfig.email_from == f"{DEFAULT_EMAIL_FROM_LOCAL_PART}@myhost" From 8df88c85e4b125b6c490e91f112fe85a156e7943 Mon Sep 17 00:00:00 2001 From: John Davis Date: Wed, 15 Feb 2023 10:02:07 -0500 Subject: [PATCH 3/7] Add activation_email to deprecated options --- lib/galaxy/config/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/galaxy/config/__init__.py b/lib/galaxy/config/__init__.py index d1995b2e952..0232485f54a 100644 --- a/lib/galaxy/config/__init__.py +++ b/lib/galaxy/config/__init__.py @@ -622,6 +622,7 @@ class GalaxyAppConfiguration(BaseAppConfiguration, CommonConfigurationMixin): "user_library_import_symlink_whitelist", "fetch_url_whitelist", "containers_resolvers_config_file", + "activation_email", ) renamed_options = { "blacklist_file": "email_domain_blocklist_file", From fd83821155fb4d6c3eb22b8c7d76e72a17504e7d Mon Sep 17 00:00:00 2001 From: John Davis Date: Wed, 15 Feb 2023 14:44:43 -0500 Subject: [PATCH 4/7] Update test/unit/config/test_config_values.py Co-authored-by: Nicola Soranzo --- test/unit/config/test_config_values.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/unit/config/test_config_values.py b/test/unit/config/test_config_values.py index bb23ddd494d..b085b05b35b 100644 --- a/test/unit/config/test_config_values.py +++ b/test/unit/config/test_config_values.py @@ -46,5 +46,7 @@ def test_base_config_if_running_not_from_source(monkeypatch): def test_assign_email_from(monkeypatch): - appconfig = config.GalaxyAppConfiguration(override_tempdir=False, galaxy_infrastructure_url="myhost") + appconfig = config.GalaxyAppConfiguration( + override_tempdir=False, galaxy_infrastructure_url="http://myhost:8080/galaxy/" + ) assert appconfig.email_from == f"{DEFAULT_EMAIL_FROM_LOCAL_PART}@myhost" From d00fdf05edc1d389c9b1131d1aef7bb956dc28ad Mon Sep 17 00:00:00 2001 From: John Davis Date: Wed, 15 Feb 2023 22:26:27 -0500 Subject: [PATCH 5/7] Remove duplication from deprecated+renamed config options Co-authored-by: Nicola Soranzo --- lib/galaxy/config/__init__.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/lib/galaxy/config/__init__.py b/lib/galaxy/config/__init__.py index 0232485f54a..de12216f1ea 100644 --- a/lib/galaxy/config/__init__.py +++ b/lib/galaxy/config/__init__.py @@ -613,17 +613,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", - "activation_email", - ) renamed_options = { "blacklist_file": "email_domain_blocklist_file", "whitelist_file": "email_domain_allowlist_file", @@ -633,6 +622,12 @@ class GalaxyAppConfiguration(BaseAppConfiguration, CommonConfigurationMixin): "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"} From cba683ac87a0eef4e7d9befc59ce0a37795406fe Mon Sep 17 00:00:00 2001 From: John Davis Date: Wed, 15 Feb 2023 23:18:28 -0500 Subject: [PATCH 6/7] Refactor setting emailfrom option; add hostname to config Co-authored-by: Nicola Soranzo --- lib/galaxy/config/__init__.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/lib/galaxy/config/__init__.py b/lib/galaxy/config/__init__.py index de12216f1ea..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 @@ -1042,9 +1043,7 @@ class GalaxyAppConfiguration(BaseAppConfiguration, CommonConfigurationMixin): if section.startswith("server:"): self.server_names.append(section.replace("server:", "", 1)) - self._set_galaxy_infrastructure_url(kwargs) - # _set_email_from() should be called AFTER _set_galaxy_infrastructure_url() - self._set_email_from() + 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 @@ -1207,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.) @@ -1225,10 +1230,15 @@ 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: - hostname = self.galaxy_infrastructure_url or socket.getfqdn() - self.email_from = f"{DEFAULT_EMAIL_FROM_LOCAL_PART}@{hostname}" + self.email_from = f"{DEFAULT_EMAIL_FROM_LOCAL_PART}@{self.hostname}" def reload_sanitize_allowlist(self, explicit=True): self.sanitize_allowlist = [] From 9658b1852460812bc34d7cb801660147924211b3 Mon Sep 17 00:00:00 2001 From: John Davis Date: Wed, 15 Feb 2023 23:22:19 -0500 Subject: [PATCH 7/7] Remove duplicate logic, use config.hostname instead Co-authored-by: Nicola Soranzo --- lib/galaxy/managers/users.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/lib/galaxy/managers/users.py b/lib/galaxy/managers/users.py index 811332ff924..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 @@ -534,10 +533,9 @@ 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, @@ -571,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."