Skip to content

Attribute list evaluation with and join condition produces incorrect results in authenticator mapping #845

@konono

Description

@konono

Description
When using authenticator maps with attribute-based triggers, attributes that are lists are currently evaluated element by element and combined using the global join_condition. This produces unexpected results when the join condition is and.

Steps to Reproduce

Raw attribute data:

2025-08-26 14:17:15,287 DEBUG    21d8a01d-1fe8-4527-bb44-2e75329fd9b4 ansible_base.authentication.utils.claims aapadmA100's attrs: {
  'objectclass': ['top', 'person', 'organizationalPerson', 'inetOrgPerson'],
  'cn': ['aapadmA100'],
  'sn': ['A100'],
  'uid': ['aapadmA100'],
  'userpassword': ['{SSHA}0idZBuDtewExj/0pcgLisBDHUr5Ih/tg'],
  'employeetype': ['developer', 'executor'],
  'auth_time': '2025-08-26T14:17:15.284587Z'
}
  1. Configure a user with attributes:

    cn: ['aapadmA100']
    employeetype: ['developer', 'executor']
  2. Configure an authenticator map with trigger condition:

    if 'aapadm' in cn and 'developer' in employeetype
  3. Authenticate the user.

Expected Behavior
The condition should evaluate to true, because:

'aapadm' in ['aapadmA100'] → True
'developer' in ['developer', 'executor'] → True

Therefore, the overall expression should succeed.

Actual Behavior
In _process_user_value, list attributes are expanded and each element is evaluated individually.
Because has_access_with_join(..., join_condition) is applied inside the loop, the global join_condition (and/or) is incorrectly applied across list elements, not just across attributes.

Relevant code excerpt:

evaluate_fn = operators[operator]

for a_user_value in user_value:
    user_str = f"{a_user_value}".casefold() if _is_case_insensitivity_enabled() else f"{a_user_value}"

    result = evaluate_fn(user_str, trigger_value)

    # <-- Problem: join_condition applied here for each element -->
    has_access = has_access_with_join(has_access, result, join_condition)

    header = f"Attr [{attribute}] value [{user_str}]"
    message = _get_operator_messages(operator, result)
    _prefixed_debug(map_id, tracking_id, f"{header} {message} [{trigger_value}], {_result_suffix(result)}")

return has_access

Evaluation steps:

  • For cn:

    _evaluate_contains("aapadmA100", "aapadm")
    # → "aapadm" in "aapadmA100" → True
    has_access = True
    
  • For employeetype:

    _evaluate_contains("developer", "developer")
    # → "developer" in "developer" → True
    
    has_access = has_access_with_join(True, True, "and") → True
    
    _evaluate_contains("executor", "developer")
    # → "developer" in "executor" → False
    has_access = has_access_with_join(True, False, "and") → False
    

Final result: False, so the map is skipped/denied, even though the user clearly has "developer" in their employeetype.

Impact
Users with multi-valued attributes cannot be matched correctly when join_condition = 'and'.
This breaks common scenarios such as matching on employeetype or other attributes that often contain multiple values.

Suggestion
List attributes should be evaluated with any(...) semantics: if any element of the list matches, the condition for that attribute should be considered true.
Only after reducing each attribute’s evaluation to a single boolean should the global join_condition (and / or) be applied across attributes.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions