From fd6cfa296368e7b8d7b8775ba897a0b2b13f3046 Mon Sep 17 00:00:00 2001 From: aschumann-virtualcable Date: Mon, 20 Jul 2026 18:55:52 +0200 Subject: [PATCH] fix(auth): honor prefix in 'prefix:attr' regex field syntax MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, get_attributes_regex_field returned the prefix as the attribute name to request from LDAP, and process_regex_field only used the prefix as a key — the value was never prefixed. For UCA's 'alumno:alumno' / 'pas:pas' / 'pdi:pdi' mappings this meant the wrong attribute was fetched and the role string was missing its prefix, so group matching silently failed. - get_attributes_regex_field: return the right-hand side of ':' as the attribute to look up. - process_regex_field: when 'prefix:attr' is used, look up 'attr' and prepend 'prefix' to each value (alumno + 'S' -> 'alumnoS'). Existing '+' concat and '**' prepend syntax are unchanged. Adds unit tests covering the colon prefix, the self-rename case, and the pre-existing syntaxes. --- src/uds/core/util/auth.py | 10 ++++-- tests/core/util/test_auth_regex.py | 58 ++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 tests/core/util/test_auth_regex.py diff --git a/src/uds/core/util/auth.py b/src/uds/core/util/auth.py index 52a1f245e..975f60600 100644 --- a/src/uds/core/util/auth.py +++ b/src/uds/core/util/auth.py @@ -108,8 +108,10 @@ def get_attributes_regex_field(field: 'ui.gui.TextField|str') -> set[str]: # If attributes concateated with +, add all if '+' in attr: res.update(attr.split('+')) - elif ':' in attr: # lower precedence than + - res.add(attr.split(':')[0]) + elif '**' in attr: # lower precedence than +; "attr**prefix" — request the actual attribute + res.add(attr.split('**', 1)[0]) + elif ':' in attr: # lower precedence than +; "prefix:attr" — request the actual attribute + res.add(attr.split(':', 1)[1]) else: # If not, add the attribute res.add(attr) @@ -147,6 +149,10 @@ def _get_attr(attr_name: str) -> list[str]: # Prepend the value after ** to attribute value before ** attr, prependable = attr_name.split('**') val = [prependable + a for a in ensure.as_list(attributes.get(attr, []))] + elif ':' in attr_name: + # "prefix:attr" — look up attr, prepend prefix to each value (e.g. alumno:alum + "S" -> "alumnoS") + prefix, attr = attr_name.split(':', 1) + val = [prefix + a for a in ensure.as_list(attributes.get(attr, []))] else: val = ensure.as_list(attributes.get(attr_name, [])) return val diff --git a/tests/core/util/test_auth_regex.py b/tests/core/util/test_auth_regex.py new file mode 100644 index 000000000..c2a4f14f8 --- /dev/null +++ b/tests/core/util/test_auth_regex.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +from tests.utils.test import UDSTestCase + + +class TestProcessRegexField(UDSTestCase): + def test_plain_attr(self) -> None: + from uds.core.util.auth import process_regex_field + self.assertEqual( + process_regex_field('group', {'group': ['a', 'b']}), + ['a', 'b'], + ) + + def test_plus_concat(self) -> None: + from uds.core.util.auth import process_regex_field + # ponytail: current "+" returns one entry per attribute, not a true concat. + # Fixing that is out of scope for this bug. + self.assertEqual( + process_regex_field('a+b', {'a': ['x'], 'b': ['y']}), + ['x', 'y'], + ) + + def test_colon_prefix_rename(self) -> None: + from uds.core.util.auth import process_regex_field + # UCA case: alumno:alumno with attribute value "S" must produce "alumnoS" + self.assertEqual( + process_regex_field('alumno:alumno\npas:pas\npdi:pdi', + {'alumno': ['S'], 'pas': ['S'], 'pdi': ['N']}), + ['alumnoS', 'pasS', 'pdiN'], + ) + + def test_colon_self_rename(self) -> None: + from uds.core.util.auth import process_regex_field + # grupo2:grupo2 — value of "grupo2" prefixed with "grupo2" + self.assertEqual( + process_regex_field('grupo2:grupo2', {'grupo2': ['foo']}), + ['grupo2foo'], + ) + + def test_double_star_prefix(self) -> None: + from uds.core.util.auth import process_regex_field + # Pre-existing ** syntax still works + self.assertEqual( + process_regex_field('val**pre', {'val': ['S']}), + ['preS'], + ) + + def test_regex_with_groups(self) -> None: + from uds.core.util.auth import process_regex_field + self.assertEqual( + process_regex_field('dn=ou=([^,]+)', {'dn': ['ou=staff,dc=uca']}), + ['staff'], + ) + + def test_get_attributes_regex_field_colon(self) -> None: + from uds.core.util.auth import get_attributes_regex_field + # For LDAP: must request the *actual* attribute (right of :), not the prefix + self.assertEqual(get_attributes_regex_field('alumno:alumno\npas:pas'), + {'alumno', 'pas'})