* consider global org and team maps for github authenticator

* consider global org and team maps for saml authenticator
This commit is contained in:
Peter Braun 2025-07-17 15:02:04 +02:00 committed by thedoubl3j
parent c4a6b28b87
commit e746589019
No known key found for this signature in database
GPG Key ID: E84C42ACF75B0768
4 changed files with 239 additions and 8 deletions

View File

@ -3,7 +3,7 @@ Unit tests for base authenticator migrator functionality.
"""
import pytest
from unittest.mock import Mock
from unittest.mock import Mock, patch
from awx.sso.utils.base_migrator import BaseAuthenticatorMigrator
@ -684,3 +684,188 @@ def test_mappers_match_structurally_edge_cases(mapper1, mapper2, expected):
result = migrator._mappers_match_structurally(mapper1, mapper2)
assert result == expected
class TestSocialAuthMapFunctions:
"""Test cases for social auth map functions."""
def setup_method(self):
"""Set up test fixtures."""
self.gateway_client = Mock()
self.command_obj = Mock()
self.migrator = BaseAuthenticatorMigrator(self.gateway_client, self.command_obj)
@patch('awx.sso.utils.base_migrator.settings')
def test_get_social_org_map_with_authenticator_specific_setting(self, mock_settings):
"""Test get_social_org_map returns authenticator-specific setting when available."""
# Set up mock settings
authenticator_map = {'org1': ['team1', 'team2']}
global_map = {'global_org': ['global_team']}
mock_settings.SOCIAL_AUTH_GITHUB_ORGANIZATION_MAP = authenticator_map
mock_settings.SOCIAL_AUTH_ORGANIZATION_MAP = global_map
# Mock getattr to return the specific setting
with patch('awx.sso.utils.base_migrator.getattr') as mock_getattr:
mock_getattr.side_effect = lambda obj, name, default=None: {
'SOCIAL_AUTH_GITHUB_ORGANIZATION_MAP': authenticator_map,
'SOCIAL_AUTH_ORGANIZATION_MAP': global_map,
}.get(name, default)
result = self.migrator.get_social_org_map('SOCIAL_AUTH_GITHUB_ORGANIZATION_MAP')
assert result == authenticator_map
# Verify it was called with the authenticator-specific setting first
mock_getattr.assert_any_call(mock_settings, 'SOCIAL_AUTH_GITHUB_ORGANIZATION_MAP', None)
@patch('awx.sso.utils.base_migrator.settings')
def test_get_social_org_map_fallback_to_global(self, mock_settings):
"""Test get_social_org_map falls back to global setting when authenticator-specific is empty."""
# Set up mock settings
global_map = {'global_org': ['global_team']}
# Mock getattr to return None for authenticator-specific, global for fallback
with patch('awx.sso.utils.base_migrator.getattr') as mock_getattr:
mock_getattr.side_effect = lambda obj, name, default=None: {
'SOCIAL_AUTH_GITHUB_ORGANIZATION_MAP': None,
'SOCIAL_AUTH_ORGANIZATION_MAP': global_map,
}.get(name, default)
result = self.migrator.get_social_org_map('SOCIAL_AUTH_GITHUB_ORGANIZATION_MAP')
assert result == global_map
# Verify both calls were made
mock_getattr.assert_any_call(mock_settings, 'SOCIAL_AUTH_GITHUB_ORGANIZATION_MAP', None)
mock_getattr.assert_any_call(mock_settings, 'SOCIAL_AUTH_ORGANIZATION_MAP', {})
@patch('awx.sso.utils.base_migrator.settings')
def test_get_social_org_map_empty_dict_fallback(self, mock_settings):
"""Test get_social_org_map returns empty dict when neither setting exists."""
# Mock getattr to return None for both settings
with patch('awx.sso.utils.base_migrator.getattr') as mock_getattr:
mock_getattr.side_effect = lambda obj, name, default=None: {'SOCIAL_AUTH_GITHUB_ORGANIZATION_MAP': None, 'SOCIAL_AUTH_ORGANIZATION_MAP': {}}.get(
name, default
)
result = self.migrator.get_social_org_map('SOCIAL_AUTH_GITHUB_ORGANIZATION_MAP')
assert result == {}
@patch('awx.sso.utils.base_migrator.settings')
def test_get_social_team_map_with_authenticator_specific_setting(self, mock_settings):
"""Test get_social_team_map returns authenticator-specific setting when available."""
# Set up mock settings
authenticator_map = {'team1': {'organization': 'org1'}}
global_map = {'global_team': {'organization': 'global_org'}}
# Mock getattr to return the specific setting
with patch('awx.sso.utils.base_migrator.getattr') as mock_getattr:
mock_getattr.side_effect = lambda obj, name, default=None: {
'SOCIAL_AUTH_GITHUB_TEAM_MAP': authenticator_map,
'SOCIAL_AUTH_TEAM_MAP': global_map,
}.get(name, default)
result = self.migrator.get_social_team_map('SOCIAL_AUTH_GITHUB_TEAM_MAP')
assert result == authenticator_map
# Verify it was called with the authenticator-specific setting first
mock_getattr.assert_any_call(mock_settings, 'SOCIAL_AUTH_GITHUB_TEAM_MAP', None)
@patch('awx.sso.utils.base_migrator.settings')
def test_get_social_team_map_fallback_to_global(self, mock_settings):
"""Test get_social_team_map falls back to global setting when authenticator-specific is empty."""
# Set up mock settings
global_map = {'global_team': {'organization': 'global_org'}}
# Mock getattr to return None for authenticator-specific, global for fallback
with patch('awx.sso.utils.base_migrator.getattr') as mock_getattr:
mock_getattr.side_effect = lambda obj, name, default=None: {'SOCIAL_AUTH_GITHUB_TEAM_MAP': None, 'SOCIAL_AUTH_TEAM_MAP': global_map}.get(
name, default
)
result = self.migrator.get_social_team_map('SOCIAL_AUTH_GITHUB_TEAM_MAP')
assert result == global_map
# Verify both calls were made
mock_getattr.assert_any_call(mock_settings, 'SOCIAL_AUTH_GITHUB_TEAM_MAP', None)
mock_getattr.assert_any_call(mock_settings, 'SOCIAL_AUTH_TEAM_MAP', {})
@patch('awx.sso.utils.base_migrator.settings')
def test_get_social_team_map_empty_dict_fallback(self, mock_settings):
"""Test get_social_team_map returns empty dict when neither setting exists."""
# Mock getattr to return None for both settings
with patch('awx.sso.utils.base_migrator.getattr') as mock_getattr:
mock_getattr.side_effect = lambda obj, name, default=None: {'SOCIAL_AUTH_GITHUB_TEAM_MAP': None, 'SOCIAL_AUTH_TEAM_MAP': {}}.get(name, default)
result = self.migrator.get_social_team_map('SOCIAL_AUTH_GITHUB_TEAM_MAP')
assert result == {}
@patch('awx.sso.utils.base_migrator.settings')
def test_get_social_org_map_with_empty_string_fallback(self, mock_settings):
"""Test get_social_org_map falls back to global when authenticator-specific is empty string."""
# Set up mock settings
global_map = {'global_org': ['global_team']}
# Mock getattr to return empty string for authenticator-specific
with patch('awx.sso.utils.base_migrator.getattr') as mock_getattr:
mock_getattr.side_effect = lambda obj, name, default=None: {
'SOCIAL_AUTH_GITHUB_ORGANIZATION_MAP': '',
'SOCIAL_AUTH_ORGANIZATION_MAP': global_map,
}.get(name, default)
result = self.migrator.get_social_org_map('SOCIAL_AUTH_GITHUB_ORGANIZATION_MAP')
assert result == global_map
@patch('awx.sso.utils.base_migrator.settings')
def test_get_social_team_map_with_empty_dict_fallback(self, mock_settings):
"""Test get_social_team_map falls back to global when authenticator-specific is empty dict."""
# Set up mock settings
global_map = {'global_team': {'organization': 'global_org'}}
# Mock getattr to return empty dict for authenticator-specific
with patch('awx.sso.utils.base_migrator.getattr') as mock_getattr:
mock_getattr.side_effect = lambda obj, name, default=None: {'SOCIAL_AUTH_GITHUB_TEAM_MAP': {}, 'SOCIAL_AUTH_TEAM_MAP': global_map}.get(
name, default
)
result = self.migrator.get_social_team_map('SOCIAL_AUTH_GITHUB_TEAM_MAP')
# Empty dict is falsy, so it should fall back to global
assert result == global_map
def test_get_social_org_map_different_authenticators(self):
"""Test get_social_org_map works with different authenticator setting names."""
test_cases = [
'SOCIAL_AUTH_GITHUB_ORGANIZATION_MAP',
'SOCIAL_AUTH_AZUREAD_OAUTH2_ORGANIZATION_MAP',
'SOCIAL_AUTH_SAML_ORGANIZATION_MAP',
'SOCIAL_AUTH_OIDC_ORGANIZATION_MAP',
]
for setting_name in test_cases:
with patch('awx.sso.utils.base_migrator.getattr') as mock_getattr:
mock_getattr.side_effect = lambda obj, name, default=None: {
setting_name: {'test_org': ['test_team']},
'SOCIAL_AUTH_ORGANIZATION_MAP': {'fallback_org': ['fallback_team']},
}.get(name, default)
result = self.migrator.get_social_org_map(setting_name)
assert result == {'test_org': ['test_team']}
def test_get_social_team_map_different_authenticators(self):
"""Test get_social_team_map works with different authenticator setting names."""
test_cases = ['SOCIAL_AUTH_GITHUB_TEAM_MAP', 'SOCIAL_AUTH_AZUREAD_OAUTH2_TEAM_MAP', 'SOCIAL_AUTH_SAML_TEAM_MAP', 'SOCIAL_AUTH_OIDC_TEAM_MAP']
for setting_name in test_cases:
with patch('awx.sso.utils.base_migrator.getattr') as mock_getattr:
mock_getattr.side_effect = lambda obj, name, default=None: {
setting_name: {'test_team': {'organization': 'test_org'}},
'SOCIAL_AUTH_TEAM_MAP': {'fallback_team': {'organization': 'fallback_org'}},
}.get(name, default)
result = self.migrator.get_social_team_map(setting_name)
assert result == {'test_team': {'organization': 'test_org'}}

View File

@ -4,6 +4,7 @@ Base authenticator migrator class.
This module defines the contract that all specific authenticator migrators must follow.
"""
from django.conf import settings
from awx.main.utils.gateway_client import GatewayAPIError
@ -512,6 +513,46 @@ class BaseAuthenticatorMigrator:
self._write_output(f' ✗ Unexpected error creating {mapper_type} mapper "{mapper_name}": {str(e)}', 'error')
return False
def get_social_org_map(self, authenticator_setting_name):
"""
Get social auth organization map with fallback to global setting.
Args:
authenticator_setting_name: Name of the authenticator-specific organization map setting
(e.g., 'SOCIAL_AUTH_GITHUB_ORGANIZATION_MAP')
Returns:
dict: Organization mapping configuration, with fallback to global setting
"""
# Try authenticator-specific setting first
authenticator_map = getattr(settings, authenticator_setting_name, None)
if authenticator_map:
return authenticator_map
# Fall back to global setting
global_map = getattr(settings, 'SOCIAL_AUTH_ORGANIZATION_MAP', {})
return global_map
def get_social_team_map(self, authenticator_setting_name):
"""
Get social auth team map with fallback to global setting.
Args:
authenticator_setting_name: Name of the authenticator-specific team map setting
(e.g., 'SOCIAL_AUTH_GITHUB_TEAM_MAP')
Returns:
dict: Team mapping configuration, with fallback to global setting
"""
# Try authenticator-specific setting first
authenticator_map = getattr(settings, authenticator_setting_name, None)
if authenticator_map:
return authenticator_map
# Fall back to global setting
global_map = getattr(settings, 'SOCIAL_AUTH_TEAM_MAP', {})
return global_map
def _write_output(self, message, style=None):
"""Write output message if command is available."""
if self.command:

View File

@ -66,8 +66,8 @@ class GitHubMigrator(BaseAuthenticatorMigrator):
continue
# If we have both key and secret, collect all settings
org_map_value = None
team_map_value = None
org_map_setting_name = None
team_map_setting_name = None
for setting_name in category_settings:
# Skip if setting_name is not a string (e.g., regex pattern)
@ -76,11 +76,15 @@ class GitHubMigrator(BaseAuthenticatorMigrator):
value = getattr(settings, setting_name, None)
config_data[setting_name] = value
# Capture org and team map values for special processing
# Capture org and team map setting names for special processing
if setting_name.endswith('_ORGANIZATION_MAP'):
org_map_value = value
org_map_setting_name = setting_name
elif setting_name.endswith('_TEAM_MAP'):
team_map_value = value
team_map_setting_name = setting_name
# Get org and team mappings using the new fallback functions
org_map_value = self.get_social_org_map(org_map_setting_name) if org_map_setting_name else {}
team_map_value = self.get_social_team_map(team_map_setting_name) if team_map_setting_name else {}
# Convert GitHub org and team mappings from AWX to the Gateway format
# Start with order 1 and maintain sequence across both org and team mappers

View File

@ -49,8 +49,9 @@ class SAMLMigrator(BaseAuthenticatorMigrator):
idps = getattr(settings, "SOCIAL_AUTH_SAML_ENABLED_IDPS", {})
security_config = getattr(settings, "SOCIAL_AUTH_SAML_SECURITY_CONFIG", {})
org_map_value = getattr(settings, "SOCIAL_AUTH_SAML_ORGANIZATION_MAP", None)
team_map_value = getattr(settings, "SOCIAL_AUTH_SAML_TEAM_MAP", None)
# Get org and team mappings using the new fallback functions
org_map_value = self.get_social_org_map("SOCIAL_AUTH_SAML_ORGANIZATION_MAP")
team_map_value = self.get_social_team_map("SOCIAL_AUTH_SAML_TEAM_MAP")
extra_data = getattr(settings, "SOCIAL_AUTH_SAML_EXTRA_DATA", None)
support_contact = getattr(settings, "SOCIAL_AUTH_SAML_SUPPORT_CONTACT", {})
technical_contact = getattr(settings, "SOCIAL_AUTH_SAML_TECHNICAL_CONTACT", {})