Skip to content

[AAP-77215] Add CleanTextMixin to DAB concrete model serializers- #4 - #1118

Open
vidyanambiar wants to merge 10 commits into
ansible:develfrom
vidyanambiar:add-validation-to-dab-serializers
Open

[AAP-77215] Add CleanTextMixin to DAB concrete model serializers- #4#1118
vidyanambiar wants to merge 10 commits into
ansible:develfrom
vidyanambiar:add-validation-to-dab-serializers

Conversation

@vidyanambiar

@vidyanambiar vidyanambiar commented Aug 31, 2026

Copy link
Copy Markdown
Member

Description

Dependencies

This PR builds on top of #1087 (merged), which introduced the CleanTextMixin, Tier 1 name validator, and Tier 2 free-text validator.

JIRA: https://redhat.atlassian.net/browse/AAP-77215

What is being changed?

Wire CleanTextMixin (#1087) into DAB's five concrete model serializers so that Authenticator, AuthenticatorMap, OAuth2Application, OAuth2AccessToken, and
RoleDefinition endpoints reject unsafe text input with HTTP 400.

  • AuthenticatorSerializer — Tier 1 validation on name
  • AuthenticatorMapSerializer — Tier 1 on name, with excluded_fields for organization/role/team (template expansion syntax)
  • OAuth2ApplicationSerializer — Tier 1 on name, Tier 2 on description
  • OAuth2TokenSerializer — Tier 2 on description
  • RoleDefinitionSerializer — Tier 1 on name, Tier 2 on description

Why is this change needed?

Addresses AAP-77215 under the parent epic AAP-74584 (DAB
Input Validation and Sanitization). CAP-1040 identified that free-text API fields accept unsafe input (XSS, shell injection, control characters). Validation is
enforced at the serializer layer so all clients (UI, API, CLI) get consistent rejection.

How does this change address the issue?

  • CleanTextMixin is placed first in the MRO of each serializer, hooking into validate() via super() chaining
  • Two serializers (AuthenticatorSerializer, AuthenticatorMapSerializer) had broken super().validate() chains (return data instead of return super().validate(data)) — fixed as a prerequisite
  • AuthenticatorMapSerializer sets excluded_fields = frozenset({'organization', 'role', 'team'}) because these fields accept {% for_attr_value() %}
    template expansion syntax
  • Subclass serializers (AuthenticatorUpdateSerializer, RoleDefinitionDetailSerializer) inherit the mixin automatically
  • Grandfathering: unchanged values on update are skipped, so pre-existing data is not blocked

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Test update
  • Refactoring (no functional changes)
  • Development environment change
  • Configuration change

Self-Review Checklist

  • I have performed a self-review of my code
  • I have added relevant comments to complex code sections
  • I have updated documentation where needed
  • I have considered the security impact of these changes
  • I have considered performance implications
  • I have thought about error handling and edge cases
  • I have tested the changes in my local environment

Related PRs

ATF Tests

ATF tests: https://gitlab.cee.redhat.com/ansible/testing/platform-services-test-suite/-/merge_requests/188

image

Testing Instructions

Prerequisites

  • A working DAB test environment (pip install -e ".[all]")

Steps to Test

Manual Testing

Please follow these instructions: https://docs.google.com/document/d/1wfGlKk4BWULIhUB9jPCw_M65NIetfzUHWUQCEWkC-wE/edit?usp=sharing

Tests

  1. Run the existing mixin unit tests:
    pytest test_app/tests/lib/serializers/test_clean_text_mixin.py -v
    pytest test_app/tests/lib/utils/test_validation.py -v

  2. Run the new integration tests for each app:
    pytest test_app/tests/authentication/test_clean_text_integration.py -v
    pytest test_app/tests/oauth2_provider/test_clean_text_integration.py -v
    pytest test_app/tests/rbac/api/test_clean_text_integration.py -v

  3. Run the existing serializer/view tests to verify no regressions:
    pytest test_app/tests/authentication/ -v
    pytest test_app/tests/oauth2_provider/ -v
    pytest test_app/tests/rbac/ -v

  4. Verify AuthenticatorMap template expansion fields still work:
    pytest test_app/tests/authentication/serializers/test_authenticator_map.py::TestAuthenticatorMapEscapeSequence -v

Expected Results

  • All tests pass
  • Invalid names (e.g. <script>alert(1)</script>) rejected with HTTP 400 on create
  • Invalid descriptions (e.g. $(rm -rf /)) rejected with HTTP 400 on create
  • Pre-existing invalid values are grandfathered on update when unchanged
  • AuthenticatorMap template expansion syntax ({% for_attr_value() %}) still accepted in organization/role/team fields

Additional Context

Dependencies

This PR builds on top of #1087, which introduced the CleanTextMixin, Tier 1 name validator, and
Tier 2 free-text validator. That PR must be merged first — the commits from it appear in this branch's history.

Required Actions

  • Requires documentation updates
  • Requires downstream repository changes
  • Blocked by PR/MR: #1087

Key design decisions

Decision Rationale
Serializer-level validation, not model-level Model validators cannot skip unchanged fields on update (grandfathering). Confirmed by POC
AAP-84927.
Reject (HTTP 400), not strip Stripping silently alters user input. Rejection gives clear feedback.
excluded_fields for AuthenticatorMap template fields organization, role, team accept {% for_attr_value() %} expansion syntax which matches the
Tier 2 template injection pattern.
Fix super().validate() chains AuthenticatorSerializer and AuthenticatorMapSerializer returned data without calling super().validate(), silently
bypassing any mixin in the MRO. Safe fix — ModelSerializer.validate() is a passthrough.

Summary by CodeRabbit

  • New Features

    • Added enhanced text validation for authentication, OAuth applications and tokens, and role definitions.
    • Added validation for authenticator mappings while allowing approved template expansion values.
    • Preserved validation for applicable authenticator configuration fields while allowing encrypted and structured pass-through values.
  • Documentation

    • Documented JSON validation exclusions and template expansion handling.
  • Tests

    • Added integration coverage for validation, updates, valid values, and grandfathered existing data.

vidyanambiar and others added 9 commits August 31, 2026 15:16
Wire CleanTextMixin into the five DAB-owned serializers so that
Authenticator, AuthenticatorMap, OAuth2Application, OAuth2AccessToken,
and RoleDefinition endpoints reject unsafe text input (Tier 1 name
allowlist, Tier 2 dangerous-pattern blocklist) with grandfathering
for unchanged values on update.

Fix broken super().validate() chains in AuthenticatorSerializer and
AuthenticatorMapSerializer so the mixin's validate() is reachable.

Set excluded_fields on AuthenticatorMapSerializer for the organization,
role, and team fields which legitimately accept template expansion
syntax.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

fix: resolve black and isort formatting issues

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Add excluded_json_keys for authenticator configuration sub-keys

Exclude encrypted sub-keys (SECRET, BIND_PASSWORD, SP_PRIVATE_KEY) and
structured pass-through data (ADDITIONAL_UNVERIFIED_ARGS) from
CleanTextMixin's JSONField scan on AuthenticatorSerializer. Non-excluded
string sub-keys like NAME are still validated as defense-in-depth.

Add code comments on both AuthenticatorSerializer.excluded_json_keys and
AuthenticatorMapSerializer.excluded_fields explaining the rationale and
referencing the validation doc. Document the full exclusion analysis in
docs/lib/validation.md.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

fix: add required organization field to OAuth2Application tests

OAuth2Application requires an organization on create. The three failing
tests were missing this field, causing a 400 before CleanTextMixin
validation could run.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Address code review findings for CleanTextMixin integration tests

Add API-level test for AuthenticatorMap (POST with dangerous name
asserts HTTP 400). Add team field to excluded_fields test so all three
expansion fields are exercised. Add comment explaining why OAuth2Token
tests omit the grandfather case.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The CleanTextMixin validation is gated behind this feature flag, so
tests expecting HTTP 400 rejections need it enabled via
@override_settings to actually trigger validation errors.

Co-Authored-By: Claude <noreply@anthropic.com>
@override_settings as a class decorator requires Django SimpleTestCase
subclasses. These are plain pytest classes, so use the pytest-django
settings fixture via an autouse fixture instead.

Co-Authored-By: Claude <noreply@anthropic.com>
…n docs

Add test_rejects_changed_invalid_name_on_update to the OAuth2Application
and RoleDefinition CleanTextMixin integration tests, mirroring the
Authenticator test's coverage of changed-value rejection on update.

Clarify docs/lib/validation.md: correct the claim that dict/list JSON
sub-keys are skipped by CleanTextMixin (they're recursed into and
validated), and document that AuthenticatorMap's organization/role/team
exclusion also bypasses validation for literal, non-templated values.

Rename test_excluded_fields_accept_dangerous_content to
test_excluded_fields_accept_invalid_content for clarity.

Assisted-by: Claude Code / Sonnet 5 (Anthropic)
… test and immutable excluded_json_keys

Add test_rejects_changed_invalid_name_on_update to
TestAuthenticatorMapCleanText, an HTTP-level negative-grandfather test
mirroring the one already present for Authenticator, OAuth2Application,
and RoleDefinition. The existing AuthenticatorMap grandfather coverage
only exercised the serializer in isolation.

Wrap AuthenticatorSerializer.excluded_json_keys in MappingProxyType,
matching CleanTextMixin's own default, so the shared class attribute
can't be mutated in place across serializer instances/requests.

Assisted-by: Claude Code / Sonnet 5 (Anthropic)
The docs/lib/validation.md code sample for AuthenticatorSerializer's
excluded_json_keys still showed the old plain-dict form after the
prior commit switched the actual code to MappingProxyType. Since this
sample is the template future plugin authors copy, the stale example
would have silently reintroduced the mutable-dict pattern.

Assisted-by: Claude Code / Sonnet 5 (Anthropic)
…ields

excluded_fields exempted these CharFields from CleanTextMixin entirely,
so literal dangerous values (not just template-expansion syntax) bypassed
validation and flowed unsanitized into Organization/Team creation on
every login. Gate the exemption on has_expansion() instead so only
genuine {% for_attr_value() %} expansion values skip validation.

Also adds a test locking in that PEM-formatted certificate config values
(SP_PUBLIC_CERT, IDP_X509_CERT) still pass Tier 2 validation, and fixes
a tautological assertion in the AuthenticatorMap expansion-field test.

Addresses the code review finding carried across iterations 1-4 of PR ansible#4.

Assisted-by: Claude Code / Sonnet 5 (Anthropic)
…ect comment

The comment claimed description is not PATCH-writable, but it is —
OAuth2TokenViewSet is a full ModelViewSet and description is not in
read_only_fields. Adds HTTP-level grandfather and rejection tests
matching the pattern used for the other four models.

Addresses PR ansible#4 review finding on test_clean_text_integration.py:77.

Co-Authored-By: Claude <noreply@anthropic.com>
The doc previously implied literal content is always validated in
expansion fields, but mixed values containing both literal text and
{% for_attr_value() %} syntax skip validation entirely. This is
intentional per the SDP scope exclusion for Jinja2 template fields.

Co-Authored-By: Claude <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: f2f41b8b-a7c9-47cc-bfbe-5b5dc1f22107

📥 Commits

Reviewing files that changed from the base of the PR and between cfd2a09 and 99e65d9.

📒 Files selected for processing (2)
  • ansible_base/authentication/serializers/authenticator.py
  • test_app/tests/authentication/test_clean_text_integration.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • ansible_base/authentication/serializers/authenticator.py

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

CleanTextMixin is integrated into authentication, OAuth2, and RBAC serializers. Authenticator configuration exclusions and AuthenticatorMap expansion handling preserve supported structured values. New integration tests cover creation and update validation.

Changes

Clean text validation

Layer / File(s) Summary
Authenticator validation and exclusions
ansible_base/authentication/serializers/authenticator.py, ansible_base/authentication/serializers/authenticator_map.py, test_app/tests/authentication/test_clean_text_integration.py, docs/lib/validation.md
Authentication serializers validate text and configuration values through CleanTextMixin. Encrypted and pass-through keys are excluded. Valid expansion expressions remain supported, while literal values are validated. Tests and documentation cover these rules.
OAuth2 serializer validation
ansible_base/oauth2_provider/serializers/application.py, ansible_base/oauth2_provider/serializers/token.py, test_app/tests/oauth2_provider/test_clean_text_integration.py
OAuth2 application and token serializers inherit CleanTextMixin. Tests cover invalid, valid, and grandfathered names and descriptions.
Role definition validation
ansible_base/rbac/api/serializers.py, test_app/tests/rbac/api/test_clean_text_integration.py
RoleDefinitionSerializer inherits CleanTextMixin. Tests cover rejected dangerous values, valid creation, and unchanged invalid values during updates.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: ⚪ Minimal · up to 99e65

The serializers now reject unsafe names and descriptions while preserving supported template fields and unchanged update values; no actionable merge-blocking risk remains after normal checks.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.82% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 39 functions across 8 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main change: adding CleanTextMixin to DAB concrete model serializers. The issue ID and suffix do not obscure the meaning.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Aug 31, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.79%. Comparing base (e5a492d) to head (99e65d9).

@@           Coverage Diff           @@
##            devel    #1118   +/-   ##
=======================================
  Coverage   94.79%   94.79%           
=======================================
  Files         259      259           
  Lines       14710    14720   +10     
  Branches     2271     2272    +1     
=======================================
+ Hits        13944    13954   +10     
  Misses        766      766           
Flag Coverage Δ
py312 94.76% <100.00%> (-0.02%) ⬇️
py312-sqlite 94.15% <100.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...e_base/authentication/serializers/authenticator.py 99.00% <100.00%> (+0.02%) ⬆️
...se/authentication/serializers/authenticator_map.py 100.00% <100.00%> (ø)
...le_base/oauth2_provider/serializers/application.py 93.47% <100.00%> (+0.14%) ⬆️
ansible_base/oauth2_provider/serializers/token.py 87.20% <100.00%> (+0.15%) ⬆️
ansible_base/rbac/api/serializers.py 92.98% <100.00%> (+0.02%) ⬆️

Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update e5a492d...99e65d9. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@vidyanambiar

vidyanambiar commented Aug 31, 2026

Copy link
Copy Markdown
Member Author

Code Review: AAP-77215 Add CleanTextMixin to DAB concrete model serializers

Verdict: READY_FOR_HUMAN_REVIEW
Score: 9.5/10 (Functionality 10, Security 9.5, Quality 9)
Risk: CRITICAL (5 security-sensitive, high-blast-radius serializers touched — no breaking changes detected)

No Critical or Major findings. 3 Minor findings, none blocking:

  1. SecurityAuthenticatorMapSerializer._run_text_validator()'s has_expansion() gate is an unanchored substring match, so a value mixing
    literal content with {% for_attr_value(...) %} skips Tier 1/2 validation entirely on organization/role/team. This was already raised and
    discussed on the predecessor PR ([AAP-77215] Add CleanTextMixin to DAB concrete model serializers prat98/django-ansible-base#4) — accepted there as an SDP-scoped, permission-gated residual risk. Recommend linking that rationale (SDP
    ANSTRAT-1756 out-of-scope section + PR Make dependencies dynamic #4 discussion) from docs/lib/validation.md so future reviewers don't re-flag it as new.
  2. QualityAuthenticatorMap's grandfather-on-update test (test_clean_text_integration.py:201) is the only one that bypasses the HTTP
    layer; the other 4 models test it via admin_api_client.patch(...).
  3. Quality — The new comment in authenticator.py:16-18 inaccurately implies nested JSON sub-keys are skipped by CleanTextMixin; they're
    actually recursed into and validated.

Verification results (MRO/super().validate() chain tracing, excluded_json_keys cross-check against every plugin's encrypted fields, CI status,
etc.) are in the full review output.

Generated with AI assistance: Claude Code / Sonnet 5 (Anthropic)

… test and JSON sub-key comment

Add an HTTP-level admin_api_client.patch() grandfather test for
AuthenticatorMap, mirroring the pattern used for Authenticator,
OAuth2Application, OAuth2AccessToken, and RoleDefinition. The
existing test exercised the serializer in isolation only; rename it
to test_grandfather_unchanged_name_on_update_at_serializer_level and
drop its unused map_serializer fixture parameter.

Correct the excluded_json_keys comment on AuthenticatorSerializer:
nested dict/list sub-keys are not skipped by CleanTextMixin's JSON
scan, they're recursed into and their string leaves are still
validated under Tier 2.

Assisted-by: Claude Code / Sonnet 5 (Anthropic)
@github-actions

Copy link
Copy Markdown

DVCS PR Check Results:

PR appears valid (JIRA key(s) found)

@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants