Skip to content

Conversation

TheRealHaoLiu
Copy link
Member

Description

This PR adds dynamic configuration support for CSRF_TRUSTED_ORIGINS by implementing a custom CSRF middleware that uses ansible_base.lib.utils.settings.get_setting instead of directly accessing Django settings.

What is being changed?

  • Added AnsibleBaseCsrfViewMiddleware class that inherits from Django's CsrfViewMiddleware
  • Added AnsibleBaseCSRFCheck class for session authentication CSRF validation
  • Modified SessionAuthentication.enforce_csrf() to use the new custom CSRF middleware
  • Added comprehensive tests for the new functionality

Why is this change needed?

  • Enables CSRF_TRUSTED_ORIGINS to be dynamically loaded from various sources via ANSIBLE_BASE_SETTINGS_FUNCTION
  • Provides consistency with other ansible-base settings that support dynamic configuration
  • Allows for runtime configuration changes without requiring Django settings modifications

How does this change address the issue?

  • Overrides all three cached properties in Django's CsrfViewMiddleware that access settings.CSRF_TRUSTED_ORIGINS
  • Ensures both middleware-level and session authentication CSRF checks use the same dynamic settings approach
  • Maintains full backward compatibility with existing Django CSRF protection mechanisms

Type of Change

  • New feature (non-breaking change which adds functionality)
  • Test update

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

Testing Instructions

Prerequisites

  • Django environment with ansible-base installed
  • Access to ANSIBLE_BASE_SETTINGS_FUNCTION configuration

Steps to Test

  1. Test Dynamic CSRF Configuration:

    # Configure a custom settings function that returns CSRF_TRUSTED_ORIGINS
    ANSIBLE_BASE_SETTINGS_FUNCTION = 'myapp.settings.get_dynamic_setting'
    
    # Use AnsibleBaseCsrfViewMiddleware in MIDDLEWARE setting
    MIDDLEWARE = [
        # ... other middleware ...
        'ansible_base.authentication.middleware.AnsibleBaseCsrfViewMiddleware',
        # ... other middleware ...
    ]
  2. Test Session Authentication CSRF:

    # Use the updated SessionAuthentication in DRF views
    from ansible_base.authentication.session import SessionAuthentication
    
    class MyAPIView(APIView):
        authentication_classes = [SessionAuthentication]
        # ... view implementation ...
  3. Run the test suite:

    python -m pytest test_app/tests/authentication/test_middleware.py -v

Expected Results

  • CSRF_TRUSTED_ORIGINS should be read using get_setting() instead of direct Django settings access
  • All existing CSRF protection functionality should work unchanged
  • Session authentication should properly validate CSRF tokens using the new middleware
  • Tests should pass, confirming that:
    • All three cached properties use get_setting
    • AnsibleBaseCSRFCheck inherits from AnsibleBaseCsrfViewMiddleware
    • Session authentication uses the custom CSRF check
    • CSRF failures raise appropriate PermissionDenied exceptions

Additional Context

Implementation Details

The implementation addresses the memory from previous interactions about Django accessing settings.CSRF_TRUSTED_ORIGINS directly in multiple places. Instead of just overriding a single property, this solution:

  1. Overrides all three cached properties that access CSRF_TRUSTED_ORIGINS:

    • csrf_trusted_origins_hosts - strips * from netloc for host validation
    • allowed_origins_exact - filters origins without wildcards
    • allowed_origin_subdomains - groups wildcard origins by scheme
  2. Maintains exact Django logic - each overridden method replicates Django's original implementation exactly, just using get_setting('CSRF_TRUSTED_ORIGINS', []) instead of settings.CSRF_TRUSTED_ORIGINS

  3. Integrates with session authentication - creates AnsibleBaseCSRFCheck that DRF's SessionAuthentication can use for CSRF validation

Performance Considerations

  • Uses @cached_property decorators to cache results and avoid repeated get_setting calls
  • Maintains the same performance characteristics as Django's original implementation
  • No additional overhead beyond the initial get_setting call per property

Security Impact

  • Positive: Enables more flexible CSRF configuration for complex deployment scenarios
  • Neutral: Maintains all existing CSRF protection mechanisms and validation logic
  • Backward Compatible: Falls back to Django settings if no custom settings function is configured

Required Actions

  • Requires documentation updates
  • Requires downstream repository changes

@Copilot Copilot AI review requested due to automatic review settings June 6, 2025 14:52
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR introduces dynamic configuration support for CSRF by implementing a custom middleware that leverages ansible_base’s get_setting, and updates session authentication to use this functionality.

  • Introduces AnsibleBaseCsrfViewMiddleware and AnsibleBaseCSRFCheck to dynamically load CSRF_TRUSTED_ORIGINS.
  • Updates SessionAuthentication.enforce_csrf() to integrate the new middleware for CSRF validation.
  • Adds comprehensive tests to verify the new dynamic behavior for CSRF handling.

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
test_app/tests/authentication/test_middleware.py Added tests for verifying the new CSRF middleware and check behavior.
ansible_base/authentication/session.py Introduced AnsibleBaseCSRFCheck and updated SessionAuthentication to use it.
ansible_base/authentication/middleware.py Added AnsibleBaseCsrfViewMiddleware to dynamically load CSRF_TRUSTED_ORIGINS.

if "*" in origin
):
allowed_origin_subdomains[parsed.scheme].append(
parsed.netloc.lstrip("*")
Copy link
Preview

Copilot AI Jun 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Consider extracting the logic for stripping wildcard characters from the netloc into a helper function to reduce duplication and improve readability.

Suggested change
parsed.netloc.lstrip("*")
self._strip_wildcard_from_netloc(parsed.netloc)

Copilot uses AI. Check for mistakes.

Django settings.
This allows the setting to be dynamically loaded from various sources
as configured by the ANSIBLE_BASE_SETTINGS_FUNCTION setting.
Copy link
Member

@rochacbruno rochacbruno Jun 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍🏻 I like the composability (is this a word?) here

Copy link
Contributor

@BrennanPaciorek BrennanPaciorek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pass it through the linter, make a Jira issue able to be picked up via DVCS, and it'll have my approval.

… use get_setting

- Add AnsibleBaseCsrfViewMiddleware that reads CSRF_TRUSTED_ORIGINS using
  ansible_base.lib.utils.settings.get_setting instead of directly from Django settings
- Override all three cached properties (csrf_trusted_origins_hosts, allowed_origins_exact,
  allowed_origin_subdomains) to use get_setting for dynamic configuration
- Add AnsibleBaseCSRFCheck class that inherits from AnsibleBaseCsrfViewMiddleware
- Modify SessionAuthentication.enforce_csrf to use AnsibleBaseCSRFCheck instead of
  Django's default CSRFCheck
- Add comprehensive tests for both middleware and session authentication CSRF functionality
- Enables CSRF_TRUSTED_ORIGINS to be dynamically loaded from various sources via
  ANSIBLE_BASE_SETTINGS_FUNCTION while maintaining backward compatibility
@TheRealHaoLiu TheRealHaoLiu force-pushed the csrf-middleware-use-get_settings branch from 4071571 to bdfc0e2 Compare June 6, 2025 19:26
Copy link

github-actions bot commented Jun 6, 2025

DVCS PR Check Results:

Could not find JIRA key(s) in PR title, branch name, or commit messages

Copy link

sonarqubecloud bot commented Jun 6, 2025

@tznamena
Copy link
Contributor

tznamena commented Jun 9, 2025

Is there a Jira work item for this work?

@AlanCoding
Copy link
Member

This seems very related to #744

@BrennanPaciorek
Copy link
Contributor

Note: this is In direct conflict with #755. Started with this one, started trying different implementations for doing the settings hack, 755 is the result.

@john-westcott-iv
Copy link
Member

Closing in favor of #755

@john-westcott-iv john-westcott-iv changed the title Add AnsibleBaseCsrfViewMiddleware and update SessionAuthentication to use get_setting [POC] Add AnsibleBaseCsrfViewMiddleware and update SessionAuthentication to use get_setting Jul 10, 2025
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.

6 participants