Skip to content

Commit d1e450a

Browse files
committed
Merge branch 'release_26.0' into dev
2 parents 331a539 + f07c946 commit d1e450a

6 files changed

Lines changed: 115 additions & 19 deletions

File tree

lib/galaxy/authnz/managers.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
AuthForbidden,
1616
AuthTokenError,
1717
)
18+
from social_core.utils import module_member
1819

1920
from galaxy import (
2021
exceptions,
@@ -34,6 +35,7 @@
3435
resource_path,
3536
)
3637
from .psa_authnz import (
38+
BACKENDS,
3739
BACKENDS_NAME,
3840
PSAAuthnz,
3941
)
@@ -161,6 +163,21 @@ def _parse_oidc_backends_config(self, config_file):
161163

162164
if len(self.oidc_backends_config) == 0:
163165
raise exceptions.ConfigurationError("No valid provider configuration parsed.")
166+
# Force-import each configured backend so missing conditional
167+
# dependencies (e.g. pkce) fail Galaxy startup with an actionable
168+
# error instead of surfacing as a silent 401 on first OIDC login
169+
# (issue #22502).
170+
for idp in self.oidc_backends_config:
171+
try:
172+
module_member(BACKENDS[idp])
173+
except ImportError as e:
174+
raise exceptions.ConfigurationError(
175+
f"Failed to import OIDC backend for provider '{idp}' "
176+
f"({BACKENDS[idp]}): {e}. This typically indicates a "
177+
"missing conditional dependency; re-run Galaxy's "
178+
"common_startup or install requirements from "
179+
"lib/galaxy/dependencies/conditional-requirements.txt."
180+
)
164181
except ImportError:
165182
raise
166183
except (etree.ParseError, exceptions.ConfigurationError) as e:

lib/galaxy/datatypes/tabular.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,10 @@ def _read_chunk(self, trans, dataset: HasFileName, offset: int, ck_size: Optiona
165165
cursor = f.read(1)
166166
except UnicodeDecodeError:
167167
raise InvalidFileFormatError("Dataset appears to contain binary data, cannot display.")
168+
except EOFError:
169+
raise InvalidFileFormatError(
170+
"Dataset appears to be a truncated or corrupt compressed file, cannot display."
171+
)
168172
last_read = f.tell()
169173
return ck_data, last_read
170174

lib/galaxy/dependencies/__init__.py

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -131,16 +131,15 @@ def collect_types(from_dict):
131131
except OSError:
132132
pass
133133

134-
# Parse oidc_backends_config_file specifically for PKCE support.
135-
self.pkce_support = False
136-
oidc_backend_conf_xml = self.config_object.oidc_backends_config_file
137-
try:
138-
for pkce_support_element in parse_xml(oidc_backend_conf_xml).iterfind("./provider/pkce_support"):
139-
if pkce_support_element.text == "true":
140-
self.pkce_support = True
141-
break
142-
except OSError:
143-
pass
134+
# Install pkce whenever OIDC is in use. PKCE can be toggled per-provider
135+
# in oidc_backends_config.xml at runtime, so tying the install decision
136+
# to any top-level OIDC signal avoids rebuilding the venv when
137+
# pkce_support is flipped (issue #22502).
138+
self.oidc_active = (
139+
asbool(self.config.get("enable_oidc", False))
140+
or bool(self.config.get("oidc_auth_pipeline"))
141+
or bool(self.config.get("oidc_auth_pipeline_extra"))
142+
)
144143

145144
# Parse error report config
146145
error_report_yml = self.config_object.error_report_file
@@ -325,7 +324,7 @@ def check_hvac(self):
325324
return "hashicorp" == self.vault_type
326325

327326
def check_pkce(self):
328-
return self.pkce_support
327+
return self.oidc_active
329328

330329
def check_rucio_clients(self):
331330
return "rucio" in self.object_stores

lib/galaxy/tools/__init__.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3881,6 +3881,14 @@ def _add_datasets_to_history(self, history, elements, datasets_visible=False):
38813881
element_object.visible = datasets_visible
38823882
history.stage_addition(element_object)
38833883

3884+
@staticmethod
3885+
def _read_text_file_lines(path: str, size_hint: int = 10000000) -> list[str]:
3886+
try:
3887+
with open(path) as fh:
3888+
return fh.readlines(size_hint)
3889+
except UnicodeDecodeError:
3890+
raise exceptions.MessageException("Please provide the file as valid UTF-8.")
3891+
38843892
def produce_outputs(self, trans: "ProvidesUserContext", out_data, output_collections, incoming, history, **kwds):
38853893
return self._outputs_dict()
38863894

@@ -4505,9 +4513,9 @@ def produce_outputs(self, trans, out_data, output_collections, incoming, history
45054513
old_elements_dict = {}
45064514
for element in elements:
45074515
old_elements_dict[element.element_identifier] = element
4516+
sort_lines = self._read_text_file_lines(hda.get_file_name())
45084517
try:
4509-
with open(hda.get_file_name()) as fh:
4510-
sorted_elements = [old_elements_dict[line.strip()] for line in fh]
4518+
sorted_elements = [old_elements_dict[line.strip()] for line in sort_lines]
45114519
except KeyError:
45124520
hdca_history_name = f"{hdca.hid}: {hdca.name}"
45134521
message = f"List of element identifiers does not match element identifiers in collection '{hdca_history_name}'"
@@ -4709,8 +4717,7 @@ def add_copied_value_to_new_elements(new_label, dce_object, columns):
47094717
new_rows[new_label] = columns
47104718

47114719
new_labels_path = new_labels_dataset_assoc.get_file_name()
4712-
with open(new_labels_path) as fh:
4713-
new_labels = fh.readlines(1024 * 1000000)
4720+
new_labels = self._read_text_file_lines(new_labels_path)
47144721
if strict and len(hdca.collection.elements) != len(new_labels):
47154722
raise exceptions.MessageException("Relabel mapping file contains incorrect number of identifiers")
47164723
if how_type in ["tabular", "tabular_extended"]:
@@ -4866,8 +4873,7 @@ def add_copied_value_to_new_elements(new_tags_dict, dce):
48664873
new_elements[dce.element_identifier] = copied_value
48674874

48684875
new_tags_path = new_tags_dataset_assoc.get_file_name()
4869-
with open(new_tags_path) as fh:
4870-
new_tags = fh.readlines(1024 * 1000000)
4876+
new_tags = self._read_text_file_lines(new_tags_path)
48714877
# We have a tabular file, where the first column is an existing element identifier,
48724878
# and the remaining columns represent new tags.
48734879
source_new_tags = (line.strip().split("\t") for line in new_tags)
@@ -4898,8 +4904,7 @@ def produce_outputs(self, trans, out_data, output_collections, incoming, history
48984904
discarded_rows = {}
48994905

49004906
filtered_path = filter_dataset_assoc.get_file_name()
4901-
with open(filtered_path) as fh:
4902-
filtered_identifiers = [i.strip() for i in fh.readlines(1024 * 1000000)]
4907+
filtered_identifiers = [i.strip() for i in self._read_text_file_lines(filtered_path)]
49034908

49044909
# If filtered_dataset_assoc is not a two-column tabular dataset we label with the current line of the dataset
49054910
for dce in hdca.collection.elements:

lib/galaxy_test/api/test_workflows.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6290,6 +6290,47 @@ def test_workflow_failed_with_message_exception(self, history_id):
62906290
assert message["workflow_step_id"] == 2
62916291
assert "Invalid new collection identifier" in message["details"]
62926292

6293+
@skip_without_tool("__RELABEL_FROM_FILE__")
6294+
def test_relabel_from_file_rejects_non_utf8_labels(self, history_id):
6295+
# Regression test: a non-UTF-8 labels file (for example UTF-16) should
6296+
# fail with a clear MessageException rather than crash on decode.
6297+
summary = self._run_workflow(
6298+
"""
6299+
class: GalaxyWorkflow
6300+
inputs:
6301+
input_collection:
6302+
collection_type: list
6303+
type: collection
6304+
relabel_file:
6305+
type: data
6306+
steps:
6307+
relabel:
6308+
tool_id: __RELABEL_FROM_FILE__
6309+
in:
6310+
input: input_collection
6311+
how|labels: relabel_file
6312+
test_data:
6313+
input_collection:
6314+
collection_type: list
6315+
elements:
6316+
- identifier: A
6317+
content: "alpha"
6318+
- identifier: B
6319+
content: "beta"
6320+
relabel_file:
6321+
value: random-file
6322+
type: File
6323+
""",
6324+
history_id=history_id,
6325+
assert_ok=False,
6326+
wait=True,
6327+
)
6328+
invocation_details = self.workflow_populator.get_invocation(summary.invocation_id, step_details=True)
6329+
assert invocation_details["state"] == "failed"
6330+
assert len(invocation_details["messages"]) == 1
6331+
message = invocation_details["messages"][0]
6332+
assert "UTF-8" in message["details"]
6333+
62936334
@skip_without_tool("__RELABEL_FROM_FILE__")
62946335
@skip_without_tool("job_properties")
62956336
@skip_without_tool("cat1")

test/unit/app/dependencies/test_deps.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,36 @@ def test_vault_hashicorp_configured():
109109
assert cds.check_hvac()
110110

111111

112+
def test_pkce_default_disabled():
113+
with _config_context() as cc:
114+
cds = cc.get_cond_deps()
115+
assert cds.check_pkce() is False
116+
117+
118+
def test_pkce_enabled_when_enable_oidc():
119+
with _config_context() as cc:
120+
cds = cc.get_cond_deps(config={"enable_oidc": True})
121+
assert cds.check_pkce() is True
122+
123+
124+
def test_pkce_disabled_when_enable_oidc_off():
125+
with _config_context() as cc:
126+
cds = cc.get_cond_deps(config={"enable_oidc": False})
127+
assert cds.check_pkce() is False
128+
129+
130+
def test_pkce_enabled_via_auth_pipeline():
131+
with _config_context() as cc:
132+
cds = cc.get_cond_deps(config={"oidc_auth_pipeline": ["galaxy.authnz.psa_authnz.verify"]})
133+
assert cds.check_pkce() is True
134+
135+
136+
def test_pkce_enabled_via_auth_pipeline_extra():
137+
with _config_context() as cc:
138+
cds = cc.get_cond_deps(config={"oidc_auth_pipeline_extra": ["galaxy.authnz.psa_authnz.verify"]})
139+
assert cds.check_pkce() is True
140+
141+
112142
@pytest.mark.parametrize(
113143
"config,expected",
114144
[

0 commit comments

Comments
 (0)