Loading lib/galaxy/managers/interactivetool.py +10 −15 Original line number Diff line number Diff line Loading @@ -320,24 +320,19 @@ class InteractiveToolManager: def target_if_active(self, trans, entry_point): if entry_point.active and not entry_point.deleted: use_it_proxy_host_cfg = ( not self.app.config.interactivetools_upstream_proxy and self.app.config.interactivetools_proxy_host ) url_parts = urlsplit(trans.request.host_url) url_host = self.app.config.interactivetools_proxy_host if use_it_proxy_host_cfg else trans.request.host url_path = url_parts.path request_host = trans.request.host if not self.app.config.interactivetools_upstream_proxy and self.app.config.interactivetools_proxy_host: request_host = self.app.config.interactivetools_proxy_host protocol = trans.request.host_url.split("//", 1)[0] if entry_point.requires_domain: url_host = f"{self.get_entry_point_subdomain(trans, entry_point)}.{url_host}" rval = f"{protocol}//{self.get_entry_point_subdomain(trans, entry_point)}.{request_host}/" if entry_point.entry_url: url_path = f"{url_path.rstrip('/')}/{entry_point.entry_url.lstrip('/')}" rval = "{}/{}".format(rval.rstrip("/"), entry_point.entry_url.lstrip("/")) else: url_path = self.get_entry_point_path(trans, entry_point) if not use_it_proxy_host_cfg: return url_path return urlunsplit((url_parts.scheme, url_host, url_path, "", "")) rval = self.get_entry_point_path(trans, entry_point) if not self.app.config.interactivetools_upstream_proxy and self.app.config.interactivetools_proxy_host: rval = f"{protocol}//{request_host}{rval}" return rval def _get_entry_point_url_elements(self, trans, entry_point): encoder = IdAsLowercaseAlphanumEncodingHelper(trans.security) Loading lib/galaxy/model/__init__.py +38 −1 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ from collections import defaultdict from collections.abc import Callable from datetime import timedelta from enum import Enum from secrets import token_hex from string import Template from typing import ( Any, Loading Loading @@ -2705,17 +2706,21 @@ class InteractiveToolEntryPoint(Base, Dictifiable, RepresentById): protocol = Column(TEXT) entry_url = Column(TEXT) requires_domain = Column(Boolean, default=True) requires_path_in_url = Column(Boolean, default=False) requires_path_in_header_named = Column(TEXT) info = Column(MutableJSONType, nullable=True) configured = Column(Boolean, default=False) deleted = Column(Boolean, default=False) created_time = Column(DateTime, default=now) modified_time = Column(DateTime, default=now, onupdate=now) label = Column(TEXT) job = relationship("Job", back_populates="interactivetool_entry_points", uselist=False) dict_collection_visible_keys = [ "id", "job_id", "name", "label", "active", "created_time", "modified_time", Loading @@ -2725,15 +2730,25 @@ class InteractiveToolEntryPoint(Base, Dictifiable, RepresentById): "id", "job_id", "name", "label", "active", "created_time", "modified_time", "output_datasets_ids", ] def __init__(self, requires_domain=True, configured=False, deleted=False, short_token=False, **kwd): def __init__( self, requires_domain=True, requires_path_in_url=False, configured=False, deleted=False, short_token=False, **kwd, ): super().__init__(**kwd) self.requires_domain = requires_domain self.requires_path_in_url = requires_path_in_url self.configured = configured self.deleted = deleted if short_token: Loading @@ -2749,6 +2764,10 @@ class InteractiveToolEntryPoint(Base, Dictifiable, RepresentById): return not self.job.finished return False @property def class_id(self): return "ep" @property def output_datasets_ids(self): return [da.dataset.id for da in self.job.output_datasets] Loading Loading @@ -10458,6 +10477,24 @@ class CleanupEventImplicitlyConvertedDatasetAssociationAssociation(Base): icda_id = Column(Integer, ForeignKey("implicitly_converted_dataset_association.id"), index=True) class CeleryUserRateLimit(Base): """ For each user stores the last time a task was scheduled for execution. Used to limit the number of tasks allowed per user per second. """ __tablename__ = "celery_user_rate_limit" user_id = Column(Integer, ForeignKey("galaxy_user.id", ondelete="CASCADE"), primary_key=True) last_scheduled_time = Column(DateTime, nullable=False) def __repr__(self): return ( f"CeleryUserRateLimit(id_type={self.id_type!r}, " f"id={self.id!r}, last_scheduled_time={self.last_scheduled_time!r})" ) # The following models (HDA, LDDA) are mapped imperatively (for details see discussion in PR #12064) # TLDR: there are issues ('metadata' property, Galaxy object wrapping) that need to be addressed separately # before these models can be mapped declaratively. Keeping them in the mapping module breaks the auth package Loading lib/galaxy/security/idencoding.py +2 −0 Original line number Diff line number Diff line Loading @@ -15,6 +15,8 @@ from galaxy.util import ( lowercase_alphanum_to_hex, smart_str, unicodify, hex_to_lowercase_alphanum, lowercase_alphanum_to_hex, ) log = logging.getLogger(__name__) Loading lib/galaxy/util/__init__.py +1 −3 Original line number Diff line number Diff line Loading @@ -1890,7 +1890,6 @@ def hex_to_lowercase_alphanum(hex_string: str) -> str: characters a-z and 0-9 """ import numpy as np return np.base_repr(int(hex_string, 16), 36).lower() Loading @@ -1900,5 +1899,4 @@ def lowercase_alphanum_to_hex(lowercase_alphanum: str) -> str: hexadecimal string """ import numpy as np return np.base_repr(int(lowercase_alphanum, 36), 16).lower() No newline at end of file lib/galaxy/webapps/galaxy/api/tool_entry_points.py +12 −5 Original line number Diff line number Diff line Loading @@ -12,6 +12,7 @@ from galaxy.model import ( InteractiveToolEntryPoint, Job, ) from galaxy.security.idencoding import IdAsLowercaseAlphanumEncodingHelper from galaxy.structured_app import StructuredApp from galaxy.web import expose_api_anonymous_and_sessionless from . import BaseGalaxyAPIController Loading Loading @@ -51,7 +52,7 @@ class ToolEntryPointsAPIController(BaseGalaxyAPIController): ) if job_id is not None: job = trans.sa_session.query(Job).get(self.decode_id(job_id)) job = trans.sa_session.get(Job, self.decode_id(job_id)) if not self.interactivetool_manager.can_access_job(trans, job): raise exceptions.ItemAccessibilityException() entry_points = job.interactivetool_entry_points Loading @@ -60,7 +61,11 @@ class ToolEntryPointsAPIController(BaseGalaxyAPIController): rval = [] for entry_point in entry_points: as_dict = self.encode_all_ids(trans, entry_point.to_dict(), True) entrypoint_id_encoder = IdAsLowercaseAlphanumEncodingHelper(trans.security) as_dict = entry_point.to_dict() as_dict["id"] = entrypoint_id_encoder.encode_id(as_dict["id"]) as_dict_no_id = {k: v for k, v in as_dict.items() if k != "id"} as_dict.update(self.encode_all_ids(trans, as_dict_no_id, True)) target = self.interactivetool_manager.target_if_active(trans, entry_point) if target: as_dict["target"] = target Loading @@ -82,7 +87,8 @@ class ToolEntryPointsAPIController(BaseGalaxyAPIController): # Because of auto id encoding needed for link from grid, the item.id keyword must be 'id' if not id: raise exceptions.RequestParameterMissingException("Must supply entry point ID.") entry_point_id = self.decode_id(id) entrypoint_id_encoder = IdAsLowercaseAlphanumEncodingHelper(trans.security) entry_point_id = entrypoint_id_encoder.decode_id(id) return {"target": self.interactivetool_manager.access_entry_point_target(trans, entry_point_id)} @expose_api_anonymous_and_sessionless Loading @@ -93,8 +99,9 @@ class ToolEntryPointsAPIController(BaseGalaxyAPIController): if not id: raise exceptions.RequestParameterMissingException("Must supply entry point id") try: entry_point_id = self.decode_id(id) entry_point = trans.sa_session.query(InteractiveToolEntryPoint).get(entry_point_id) entrypoint_id_encoder = IdAsLowercaseAlphanumEncodingHelper(trans.security) entry_point_id = entrypoint_id_encoder.decode_id(id) entry_point = trans.sa_session.get(InteractiveToolEntryPoint, entry_point_id) except Exception: raise exceptions.RequestParameterInvalidException("entry point invalid") if self.app.interactivetool_manager.can_access_entry_point(trans, entry_point): Loading Loading
lib/galaxy/managers/interactivetool.py +10 −15 Original line number Diff line number Diff line Loading @@ -320,24 +320,19 @@ class InteractiveToolManager: def target_if_active(self, trans, entry_point): if entry_point.active and not entry_point.deleted: use_it_proxy_host_cfg = ( not self.app.config.interactivetools_upstream_proxy and self.app.config.interactivetools_proxy_host ) url_parts = urlsplit(trans.request.host_url) url_host = self.app.config.interactivetools_proxy_host if use_it_proxy_host_cfg else trans.request.host url_path = url_parts.path request_host = trans.request.host if not self.app.config.interactivetools_upstream_proxy and self.app.config.interactivetools_proxy_host: request_host = self.app.config.interactivetools_proxy_host protocol = trans.request.host_url.split("//", 1)[0] if entry_point.requires_domain: url_host = f"{self.get_entry_point_subdomain(trans, entry_point)}.{url_host}" rval = f"{protocol}//{self.get_entry_point_subdomain(trans, entry_point)}.{request_host}/" if entry_point.entry_url: url_path = f"{url_path.rstrip('/')}/{entry_point.entry_url.lstrip('/')}" rval = "{}/{}".format(rval.rstrip("/"), entry_point.entry_url.lstrip("/")) else: url_path = self.get_entry_point_path(trans, entry_point) if not use_it_proxy_host_cfg: return url_path return urlunsplit((url_parts.scheme, url_host, url_path, "", "")) rval = self.get_entry_point_path(trans, entry_point) if not self.app.config.interactivetools_upstream_proxy and self.app.config.interactivetools_proxy_host: rval = f"{protocol}//{request_host}{rval}" return rval def _get_entry_point_url_elements(self, trans, entry_point): encoder = IdAsLowercaseAlphanumEncodingHelper(trans.security) Loading
lib/galaxy/model/__init__.py +38 −1 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ from collections import defaultdict from collections.abc import Callable from datetime import timedelta from enum import Enum from secrets import token_hex from string import Template from typing import ( Any, Loading Loading @@ -2705,17 +2706,21 @@ class InteractiveToolEntryPoint(Base, Dictifiable, RepresentById): protocol = Column(TEXT) entry_url = Column(TEXT) requires_domain = Column(Boolean, default=True) requires_path_in_url = Column(Boolean, default=False) requires_path_in_header_named = Column(TEXT) info = Column(MutableJSONType, nullable=True) configured = Column(Boolean, default=False) deleted = Column(Boolean, default=False) created_time = Column(DateTime, default=now) modified_time = Column(DateTime, default=now, onupdate=now) label = Column(TEXT) job = relationship("Job", back_populates="interactivetool_entry_points", uselist=False) dict_collection_visible_keys = [ "id", "job_id", "name", "label", "active", "created_time", "modified_time", Loading @@ -2725,15 +2730,25 @@ class InteractiveToolEntryPoint(Base, Dictifiable, RepresentById): "id", "job_id", "name", "label", "active", "created_time", "modified_time", "output_datasets_ids", ] def __init__(self, requires_domain=True, configured=False, deleted=False, short_token=False, **kwd): def __init__( self, requires_domain=True, requires_path_in_url=False, configured=False, deleted=False, short_token=False, **kwd, ): super().__init__(**kwd) self.requires_domain = requires_domain self.requires_path_in_url = requires_path_in_url self.configured = configured self.deleted = deleted if short_token: Loading @@ -2749,6 +2764,10 @@ class InteractiveToolEntryPoint(Base, Dictifiable, RepresentById): return not self.job.finished return False @property def class_id(self): return "ep" @property def output_datasets_ids(self): return [da.dataset.id for da in self.job.output_datasets] Loading Loading @@ -10458,6 +10477,24 @@ class CleanupEventImplicitlyConvertedDatasetAssociationAssociation(Base): icda_id = Column(Integer, ForeignKey("implicitly_converted_dataset_association.id"), index=True) class CeleryUserRateLimit(Base): """ For each user stores the last time a task was scheduled for execution. Used to limit the number of tasks allowed per user per second. """ __tablename__ = "celery_user_rate_limit" user_id = Column(Integer, ForeignKey("galaxy_user.id", ondelete="CASCADE"), primary_key=True) last_scheduled_time = Column(DateTime, nullable=False) def __repr__(self): return ( f"CeleryUserRateLimit(id_type={self.id_type!r}, " f"id={self.id!r}, last_scheduled_time={self.last_scheduled_time!r})" ) # The following models (HDA, LDDA) are mapped imperatively (for details see discussion in PR #12064) # TLDR: there are issues ('metadata' property, Galaxy object wrapping) that need to be addressed separately # before these models can be mapped declaratively. Keeping them in the mapping module breaks the auth package Loading
lib/galaxy/security/idencoding.py +2 −0 Original line number Diff line number Diff line Loading @@ -15,6 +15,8 @@ from galaxy.util import ( lowercase_alphanum_to_hex, smart_str, unicodify, hex_to_lowercase_alphanum, lowercase_alphanum_to_hex, ) log = logging.getLogger(__name__) Loading
lib/galaxy/util/__init__.py +1 −3 Original line number Diff line number Diff line Loading @@ -1890,7 +1890,6 @@ def hex_to_lowercase_alphanum(hex_string: str) -> str: characters a-z and 0-9 """ import numpy as np return np.base_repr(int(hex_string, 16), 36).lower() Loading @@ -1900,5 +1899,4 @@ def lowercase_alphanum_to_hex(lowercase_alphanum: str) -> str: hexadecimal string """ import numpy as np return np.base_repr(int(lowercase_alphanum, 36), 16).lower() No newline at end of file
lib/galaxy/webapps/galaxy/api/tool_entry_points.py +12 −5 Original line number Diff line number Diff line Loading @@ -12,6 +12,7 @@ from galaxy.model import ( InteractiveToolEntryPoint, Job, ) from galaxy.security.idencoding import IdAsLowercaseAlphanumEncodingHelper from galaxy.structured_app import StructuredApp from galaxy.web import expose_api_anonymous_and_sessionless from . import BaseGalaxyAPIController Loading Loading @@ -51,7 +52,7 @@ class ToolEntryPointsAPIController(BaseGalaxyAPIController): ) if job_id is not None: job = trans.sa_session.query(Job).get(self.decode_id(job_id)) job = trans.sa_session.get(Job, self.decode_id(job_id)) if not self.interactivetool_manager.can_access_job(trans, job): raise exceptions.ItemAccessibilityException() entry_points = job.interactivetool_entry_points Loading @@ -60,7 +61,11 @@ class ToolEntryPointsAPIController(BaseGalaxyAPIController): rval = [] for entry_point in entry_points: as_dict = self.encode_all_ids(trans, entry_point.to_dict(), True) entrypoint_id_encoder = IdAsLowercaseAlphanumEncodingHelper(trans.security) as_dict = entry_point.to_dict() as_dict["id"] = entrypoint_id_encoder.encode_id(as_dict["id"]) as_dict_no_id = {k: v for k, v in as_dict.items() if k != "id"} as_dict.update(self.encode_all_ids(trans, as_dict_no_id, True)) target = self.interactivetool_manager.target_if_active(trans, entry_point) if target: as_dict["target"] = target Loading @@ -82,7 +87,8 @@ class ToolEntryPointsAPIController(BaseGalaxyAPIController): # Because of auto id encoding needed for link from grid, the item.id keyword must be 'id' if not id: raise exceptions.RequestParameterMissingException("Must supply entry point ID.") entry_point_id = self.decode_id(id) entrypoint_id_encoder = IdAsLowercaseAlphanumEncodingHelper(trans.security) entry_point_id = entrypoint_id_encoder.decode_id(id) return {"target": self.interactivetool_manager.access_entry_point_target(trans, entry_point_id)} @expose_api_anonymous_and_sessionless Loading @@ -93,8 +99,9 @@ class ToolEntryPointsAPIController(BaseGalaxyAPIController): if not id: raise exceptions.RequestParameterMissingException("Must supply entry point id") try: entry_point_id = self.decode_id(id) entry_point = trans.sa_session.query(InteractiveToolEntryPoint).get(entry_point_id) entrypoint_id_encoder = IdAsLowercaseAlphanumEncodingHelper(trans.security) entry_point_id = entrypoint_id_encoder.decode_id(id) entry_point = trans.sa_session.get(InteractiveToolEntryPoint, entry_point_id) except Exception: raise exceptions.RequestParameterInvalidException("entry point invalid") if self.app.interactivetool_manager.can_access_entry_point(trans, entry_point): Loading