-
Notifications
You must be signed in to change notification settings - Fork 81
[AAP-49757] Add fallback authentication support to local authenticator #868
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
john-westcott-iv
wants to merge
19
commits into
ansible:devel
Choose a base branch
from
john-westcott-iv:AAP-49757
base: devel
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
07155e6 to
7c0c75c
Compare
tznamena
reviewed
Oct 20, 2025
AlanCoding
reviewed
Oct 20, 2025
bhavenst
reviewed
Oct 20, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this maybe get a blurb in docs/apps/authentication?
bhavenst
approved these changes
Oct 23, 2025
- Move _convert_to_seconds from local.py authenticator to reusable ansible_base/lib/utils/duration.py module - Add type hints: duration_string: Optional[str], default: int - Update function to support negative durations (validation left to callers) - Fix parsing logic to correctly handle simple integer strings like '0' - Relocate tests to test_app/tests/lib/utils/test_duration.py - Consolidate tests into parameterized test suite (62 test cases) - Achieve 100% line coverage with comprehensive edge case testing - Update local.py authenticator to import and use new utility function Co-authored-by: Claude <claude@anthropic.com>
- Add fallback_authentication configuration field (ListField of module paths) - Implement _load_fallback_plugin() to dynamically load FallbackAuthenticator classes - Implement _try_fallback_authenticators() to orchestrate fallback attempts - Fallback authentication only attempted when primary auth fails - Each fallback receives database_instance and configuration parameters - Comprehensive test coverage for plugin loading, orchestration, and integration - Tests include edge cases, error handling, and parallel execution safety - Moved duration conversion tests (test_duration.py removed as functionality moved to lib) Co-authored-by: Claude <claude@anthropic.com>
- Replace multiple if-elif conditions with single regex pattern - Pattern validates proper Python module path format - Each segment must start with letter or underscore - Must have at least one dot separating segments - Consolidate error messages for clearer user feedback Co-authored-by: Claude <claude@anthropic.com>
- Create test_app/tests/lib/utils/test_duration.py with 79 tests - Parameterized tests for valid durations (all units: s/m/h/d/w) - Parameterized tests for invalid inputs with custom defaults - Test positive and negative values - Test case-insensitive unit support - Test edge cases (zero values, lone dash, None input) - Test consistency across multiple calls - Achieve 82% code coverage with branch coverage Co-authored-by: Claude <claude@anthropic.com>
- Move MODULE_PATH_PATTERN to module level as a constant - Compile regex pattern once at module load instead of every validate() call - Improves performance by avoiding repeated regex compilation - All tests pass, no functional changes Co-authored-by: Claude <claude@anthropic.com>
- Add DURATION_CHAR_TO_SECONDS dictionary for unit-to-seconds mapping - Pre-compile DURATION_RE regex pattern at module level for performance - Support negative durations in regex pattern (^(-?\d+)([smhdw]?)$) - Add explicit None handling before regex match - Use walrus operator (:=) for cleaner match assignment - Default to 's' unit when no unit specified (or 's') - Add comprehensive try-except to catch all edge cases - All 79 tests pass with 88% code coverage Co-authored-by: Claude <claude@anthropic.com>
…ation - Consolidated test_convert_to_seconds_default_parameter, test_convert_to_seconds_zero_default, and test_convert_to_seconds_negative_default into a single parameterized test - Parameterized test_convert_to_seconds_edge_case_just_minus with multiple default values - Parameterized test_convert_to_seconds_edge_case_zero_values to test all zero unit types - Reduced code duplication while maintaining comprehensive test coverage - Test count increased from 79 to 89 due to additional parameterized cases
- Renamed all test functions to use clearer names: - test_convert_to_seconds_valid -> test_convert_to_seconds_valid_inputs - test_convert_to_seconds_invalid -> test_convert_to_seconds_invalid_inputs - Renamed all parameter names to be more descriptive: - duration_string -> duration_input, invalid_input - expected -> expected_seconds, expected_result - default -> default_value - Added pytest.param() with explicit id for every test case - IDs now clearly describe what is being tested (e.g., 'positive_15_seconds', 'invalid_string_with_default_10') - Merged edge case tests into main parameterized tests with descriptive IDs - Improved consistency test to validate expected values, not just consistency - All 83 tests pass with clear, readable test output
- Added test_convert_to_seconds_valid_inputs_ignore_default test function - Tests verify that when parsing succeeds, the default parameter is properly ignored - Covers 7 scenarios: positive/negative/zero values across different units - Addresses gap in test coverage where custom defaults were only tested with invalid inputs - All 76 tests pass
- Validate that default is an integer (excluding booleans which are int subclass) - Log warning with stack trace when invalid default is provided - Automatically fall back to default value of 10 - Add comprehensive tests for boolean default edge cases - Consolidate default behavior tests into single parameterized function The stack_info=True in logger.warning() provides developers with a full stack trace showing exactly where convert_to_seconds() was called with an invalid default, making debugging much easier while still being defensive by allowing the function to continue. Note: typeguard already prevents most invalid types (str, float, list, dict, None) from reaching the function, so we only need to explicitly handle booleans which pass isinstance(x, int) checks.
This commit introduces a new centralized import utility and refactors
existing code to use it, eliminating duplication across multiple modules.
New Utility Function:
- Created ansible_base.lib.utils.imports.import_object()
- Unified interface for dynamically importing objects from modules
- Supports two formats: 'module.path.Class' or ('module.path', 'Class')
- Uses regex validation (FULL_IMPORT_PATTERN) to ensure valid Python paths
- Raises ValueError for invalid paths, ImportError for missing modules,
and AttributeError for missing attributes
Refactored Modules:
- local.py: Removed _load_fallback_plugin(), now uses import_object directly
- settings.py: get_function_from_setting() uses import_object
- authenticator_plugins/utils.py: get_authenticator_class() uses import_object
- django_app_api.py: Updated to use import_object directly
Exception Handling:
- Updated exception handlers to catch specific exceptions (ValueError,
ImportError, AttributeError) instead of broad Exception catches
- Improved error specificity in settings.py and utils.py
Deprecation:
- Marked get_from_import() as deprecated with DeprecationWarning
- Added Sphinx-style deprecation notice in docstring
- Updated internal usage and tests to use import_object directly
- Function remains for backward compatibility with external code
Testing:
- Added comprehensive test suite in test_app/tests/lib/utils/test_imports.py
- 22 parameterized tests covering valid/invalid imports, edge cases,
standard library modules, and error conditions
- Updated test_views.py mock to target import_object
Benefits:
- Eliminates code duplication across 4+ modules
- Single source of truth for dynamic imports
- Better error messages with regex validation
- Consistent error handling across the codebase
- Improved maintainability and testability
…ack_plugin Updated all tests that were mocking _load_fallback_plugin (which was removed during refactoring) to now mock import_object from ansible_base.lib.utils.imports. Changes: - TestLoadFallbackPlugin: Updated to test import_object integration instead of the removed _load_fallback_plugin method - TestTryFallbackAuthenticators: All 10 tests now mock import_object with proper side_effect functions that accept (path, attr) parameters - TestAuthenticateIntegration: Updated test_successful_primary_auth_skips_fallback - TestParallelExecutionSafety: Updated test_fallback_state_not_shared - TestEdgeCases: Updated 3 tests (kwargs, long list, special characters) All mock functions updated to match import_object signature (path, attr) instead of the old _load_fallback_plugin signature (path). This fixes 16 failing tests in the CI.
The previous commit (113c4b0) incorrectly added database_instance and configuration parameters back to fallback authenticator instantiation. This was wrong - our design doesn't pass these parameters. Changes: 1. local.py: Remove database_instance and configuration params from fallback_class() call - fallback authenticators don't need them 2. test_local.py: Replace test_fallback_passes_correct_parameters with test_fallback_instantiation that just verifies instantiation works, not that parameters are passed This fixes the test failure and aligns with our actual architecture where fallback authenticators (like Controller fallback) are simple classes with just an authenticate() method.
Replace [a-zA-Z0-9_] with \w for better readability and to satisfy SonarCloud code quality requirements. Changes: - local.py: MODULE_PATH_PATTERN now uses \w for word characters - imports.py: FULL_IMPORT_PATTERN now uses \w for word characters Both patterns still correctly validate: - First character must be letter or underscore [a-zA-Z_] - Subsequent characters can be word characters (\w = [a-zA-Z0-9_]) All 44 tests in test_local.py and 22 tests in test_imports.py pass.
- Move MODULE_PATH_PATTERN from local.py to imports.py - Factor out [a-zA-Z_] pattern into _IDENTIFIER_START constant - Use _IDENTIFIER_START in _MODULE_SEGMENT pattern - Reuse _MODULE_SEGMENT in both MODULE_PATH_PATTERN and FULL_IMPORT_PATTERN - Eliminates all duplication of [a-zA-Z_] to satisfy SonarCloud This improves maintainability and ensures consistency across all Python identifier validation patterns. Co-authored-by: Claude <claude@anthropic.com>
This reverts commit e4be818.
16c9cec to
e8da3f4
Compare
|
DVCS PR Check Results: PR appears valid (JIRA key(s) found) |
|
BrennanPaciorek
approved these changes
Oct 23, 2025
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.



Description
This PR adds pluggable fallback authentication support to the local authenticator and includes several code quality improvements and utility refactorings discovered during implementation.
Primary Feature: Fallback Authentication System
What is being changed?
fallback_authenticationconfiguration field (array of module paths) toLocalConfigurationFallbackAuthenticatorclasses with fixed naming convention_try_fallback_authenticators()to orchestrate fallback authentication attemptsauthenticate()methodWhy is this change needed?
How does this change address the issue?
FallbackAuthenticatorclass name makes configuration simpleCode Quality Improvements
1. Duration Utility Refactoring
_convert_to_secondsfrom local authenticator to reusableansible_base/lib/utils/duration.pyNonehandlingdefaultparameter (type checking with warnings)2. Import Utility Consolidation
ansible_base/lib/utils/imports.pywith unifiedimport_object()functionget_from_import()gracefully with DeprecationWarning3. Regex Pattern Optimization
[a-zA-Z0-9_]with concise\wcharacter class syntaxConfiguration Example
{ "fallback_authentication": [ "aap_gateway_api.authentication.fallbacks.controller", "custom_app.authentication.fallbacks.ldap" ] }Plugin Contract
Each fallback module must export a
FallbackAuthenticatorclass with:__init__()constructor (no parameters required)authenticate(request, username, password, **kwargs)method returningUserorNoneSecurity Considerations
Type of Change
Testing
Fallback Authentication Tests (44 tests)
authenticate()methodDuration Utility Tests (79 test cases)
Import Utility Tests (22 test cases)
Test Results
Dependencies
This PR is required by:
fallback_authenticationconfigurationaap_gateway_api.authentication.fallbacks.controller.FallbackAuthenticatorAdditional Context
Architecture Benefits
Backward Compatibility
get_from_import()deprecated but still functional for external codeCo-authored-by: Claude claude@anthropic.com