diff --git a/client/package.json b/client/package.json index 07cb75d90e3..fef824580f5 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "@galaxyproject/galaxy-client", - "version": "23.0.0", + "version": "23.1.1", "description": "Galaxy client application build system", "keywords": [ "galaxy" diff --git a/client/src/components/User/UserPreferences.vue b/client/src/components/User/UserPreferences.vue index 327a2c17ff4..e432f8a506b 100644 --- a/client/src/components/User/UserPreferences.vue +++ b/client/src/components/User/UserPreferences.vue @@ -48,7 +48,7 @@ description="Add or modify the configuration that grants Galaxy to access your cloud-based resources." to="/user/cloud_auth" /> 0: self.prune_history_audit_task = IntervalTask( func=lambda: galaxy.model.HistoryAudit.prune(self.model.session), diff --git a/lib/galaxy/authnz/custos_authnz.py b/lib/galaxy/authnz/custos_authnz.py index 8f7b65d5de8..36d380caf12 100644 --- a/lib/galaxy/authnz/custos_authnz.py +++ b/lib/galaxy/authnz/custos_authnz.py @@ -218,17 +218,7 @@ class OIDCAuthnzBase(IdentityProvider): existing_user = trans.sa_session.query(User).filter_by(email=email).first() if not user: if existing_user: - # If there is only a single external authentication - # provider in use, trust the user provided and - # automatically associate. - # TODO: Future work will expand on this and provide an - # interface for when there are multiple auth providers - # allowing explicit authenticated association. - if ( - trans.app.config.enable_oidc - and len(trans.app.config.oidc) == 1 - and len(trans.app.auth_manager.authenticators) == 0 - ): + if trans.app.config.fixed_delegated_auth: user = existing_user else: message = f"There already exists a user with email {email}. To associate this external login, you must first be logged in as that existing account." @@ -260,7 +250,9 @@ class OIDCAuthnzBase(IdentityProvider): refresh_expiration_time=refresh_expiration_time, ) label = self.config.label - if existing_user and existing_user != user: + if trans.app.config.fixed_delegated_auth: + redirect_url = login_redirect_url + elif existing_user and existing_user != user: redirect_url = ( f"{login_redirect_url}user/external_ids" f"?email_exists={email}" diff --git a/lib/galaxy/config/__init__.py b/lib/galaxy/config/__init__.py index ac3f3a73ffc..70f43efd922 100644 --- a/lib/galaxy/config/__init__.py +++ b/lib/galaxy/config/__init__.py @@ -868,6 +868,7 @@ class GalaxyAppConfiguration(BaseAppConfiguration, CommonConfigurationMixin): self.builds_file_path = os.path.join(self.tool_data_path, self.builds_file_path) self.len_file_path = os.path.join(self.tool_data_path, self.len_file_path) self.oidc: Dict[str, Dict] = {} + self.fixed_delegated_auth: bool = False self.integrated_tool_panel_config = self._in_managed_config_dir(self.integrated_tool_panel_config) integrated_tool_panel_tracking_directory = kwargs.get("integrated_tool_panel_tracking_directory") if integrated_tool_panel_tracking_directory: diff --git a/lib/galaxy/job_execution/setup.py b/lib/galaxy/job_execution/setup.py index 7ab9c034253..de321944418 100644 --- a/lib/galaxy/job_execution/setup.py +++ b/lib/galaxy/job_execution/setup.py @@ -1,11 +1,13 @@ """Utilities to help job and tool code setup jobs.""" import json import os +import threading from typing import ( Any, cast, Dict, List, + NamedTuple, Optional, Tuple, Union, @@ -38,6 +40,27 @@ OutputHdasAndType = Dict[str, Tuple[DatasetInstance, DatasetPath]] OutputPaths = List[DatasetPath] +class JobOutput(NamedTuple): + output_name: str + dataset: DatasetInstance + dataset_path: DatasetPath + + +class JobOutputs(threading.local): + def __init__(self) -> None: + super().__init__() + self.output_hdas_and_paths: Optional[OutputHdasAndType] = None + self.output_paths: Optional[OutputPaths] = None + + @property + def populated(self) -> bool: + return self.output_hdas_and_paths is not None + + def set_job_outputs(self, job_outputs: List[JobOutput]) -> None: + self.output_paths = [t[2] for t in job_outputs] + self.output_hdas_and_paths = {t.output_name: (t.dataset, t.dataset_path) for t in job_outputs} + + class JobIO(Dictifiable): dict_collection_visible_keys = ( "job_id", @@ -99,7 +122,6 @@ class JobIO(Dictifiable): user_context_instance = user_context self.user_context = user_context_instance self.sa_session = sa_session - self.job = job self.job_id = job.id self.working_directory = working_directory self.outputs_directory = outputs_directory @@ -121,25 +143,33 @@ class JobIO(Dictifiable): self.is_task = is_task self.tool_source = tool_source self.tool_source_class = tool_source_class - self._output_paths: Optional[OutputPaths] = None - self._output_hdas_and_paths: Optional[OutputHdasAndType] = None + self.job_outputs = JobOutputs() self._dataset_path_rewriter: Optional[DatasetPathRewriter] = None + @property + def job(self): + return self.sa_session.get(Job, self.job_id) + @classmethod def from_json(cls, path, sa_session): with open(path) as job_io_serialized: io_dict = json.load(job_io_serialized) - return cls.from_dict(io_dict=io_dict, sa_session=sa_session) - - @classmethod - def from_dict(cls, io_dict, sa_session): - io_dict.pop("model_class") + # Drop in 24.0 + io_dict.pop("model_class", None) job_id = io_dict.pop("job_id") job = sa_session.query(Job).get(job_id) return cls(sa_session=sa_session, job=job, **io_dict) + @classmethod + def from_dict(cls, io_dict, sa_session): + # Drop in 24.0 + io_dict.pop("model_class", None) + return cls(sa_session=sa_session, **io_dict) + def to_dict(self): io_dict = super().to_dict() + # dict_for will always add `model_class`, we don't need or want it + io_dict.pop("model_class") io_dict["user_context"] = self.user_context.to_dict() return io_dict @@ -165,15 +195,15 @@ class JobIO(Dictifiable): @property def output_paths(self) -> OutputPaths: - if self._output_paths is None: + if not self.job_outputs.populated: self.compute_outputs() - return cast(OutputPaths, self._output_paths) + return cast(OutputPaths, self.job_outputs.output_paths) @property def output_hdas_and_paths(self) -> OutputHdasAndType: - if self._output_hdas_and_paths is None: + if not self.job_outputs.populated: self.compute_outputs() - return cast(OutputHdasAndType, self._output_hdas_and_paths) + return cast(OutputHdasAndType, self.job_outputs.output_hdas_and_paths) def get_input_dataset_fnames(self, ds: DatasetInstance) -> List[str]: filenames = [ds.get_file_name()] @@ -241,22 +271,21 @@ class JobIO(Dictifiable): special = self.sa_session.query(JobExportHistoryArchive).filter_by(job=job).first() false_path = None - results = [] + job_outputs = [] for da in job.output_datasets + job.output_library_datasets: da_false_path = dataset_path_rewriter.rewrite_dataset_path(da.dataset, "output") mutable = da.dataset.dataset.external_filename is None dataset_path = DatasetPath( da.dataset.dataset.id, da.dataset.get_file_name(), false_path=da_false_path, mutable=mutable ) - results.append((da.name, da.dataset, dataset_path)) + job_outputs.append(JobOutput(da.name, da.dataset, dataset_path)) - self._output_paths = [t[2] for t in results] - self._output_hdas_and_paths = {t[0]: t[1:] for t in results} if special: false_path = dataset_path_rewriter.rewrite_dataset_path(special, "output") dsp = DatasetPath(special.dataset.id, special.dataset.get_file_name(), false_path) - self._output_paths.append(dsp) - self._output_hdas_and_paths["output_file"] = (special.fda, dsp) + job_outputs.append(JobOutput("output_file", special.fda, dsp)) + + self.job_outputs.set_job_outputs(job_outputs) def get_output_file_id(self, file: str) -> Optional[int]: for dp in self.output_paths: diff --git a/lib/galaxy/managers/configuration.py b/lib/galaxy/managers/configuration.py index c3ea10c0b4d..7a955523308 100644 --- a/lib/galaxy/managers/configuration.py +++ b/lib/galaxy/managers/configuration.py @@ -217,6 +217,7 @@ class ConfigSerializer(base.ModelSerializer): "instance_access_url": _use_config, "organization_name": _use_config, "organization_url": _use_config, + "fixed_delegated_auth": _defaults_to(False), } diff --git a/lib/galaxy/tool_util/xsd/galaxy.xsd b/lib/galaxy/tool_util/xsd/galaxy.xsd index 199e83e82e5..1a6649becbd 100644 --- a/lib/galaxy/tool_util/xsd/galaxy.xsd +++ b/lib/galaxy/tool_util/xsd/galaxy.xsd @@ -1719,7 +1719,7 @@ it may be inconvenient to upload the entiry file and this can be used instead. - Type of reference - currently ``bio.tools`` and ``bioconductor`` are + Type of reference - currently ``bio.tools``, ``bioconductor``, and ``biii`` are the only supported options. @@ -7403,6 +7403,7 @@ the only supported options. + diff --git a/lib/galaxy/web/framework/base.py b/lib/galaxy/web/framework/base.py index fef6c377cc6..70b5bb769e2 100644 --- a/lib/galaxy/web/framework/base.py +++ b/lib/galaxy/web/framework/base.py @@ -222,6 +222,12 @@ class WebApplication: rc = routes.request_config() rc.mapper = self.mapper rc.mapper_dict = map_match + server_port = environ["SERVER_PORT"] + if isinstance(server_port, int): + # Workaround bug in the routes package, which would concatenate this + # without casting to str in + # https://github.com/bbangert/routes/blob/c4d5a5fb693ce8dc7cf5dbc591861acfc49d5c23/routes/__init__.py#L73 + environ["SERVER_PORT"] = str(server_port) rc.environ = environ # Setup the transaction trans = self.transaction_factory(environ) diff --git a/package.json b/package.json index cefe179d0bc..8f48deaef7e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@galaxyproject/galaxy", - "version": "1.0.0", + "version": "23.1.0", "description": ".. figure:: https://galaxyproject.org/images/galaxy-logos/galaxy_project_logo.jpg :alt: Galaxy Logo", "main": "index.js", "devDependencies": {}, @@ -18,8 +18,7 @@ }, "homepage": "https://github.com/galaxyproject/galaxy#readme", "dependencies": { - "@galaxyproject/galaxy-client": "^23.0.0", - "cpy-cli": "^4.2.0", - "vuedraggable": "^2.24.3" + "@galaxyproject/galaxy-client": "^23.1.1", + "cpy-cli": "^5.0.0" } } diff --git a/test/unit/app/authnz/test_custos_authnz.py b/test/unit/app/authnz/test_custos_authnz.py index adda2ebcb11..23a3964ea98 100644 --- a/test/unit/app/authnz/test_custos_authnz.py +++ b/test/unit/app/authnz/test_custos_authnz.py @@ -97,6 +97,7 @@ class TestCustosAuthnz(TestCase): self.trans.app.config.enable_oidc = True self.trans.app.config.oidc = [] self.trans.app.auth_manager.authenticators = [] + self.trans.app.config.fixed_delegated_auth = False @property def test_id_token(self):