Skip to content

Commit 670acf3

Browse files
authored
Merge pull request #104 from onelogin/copilot/fix-import-error-onelogin-module
Add SAML2 compatibility module with intelligent namespace conflict detection
2 parents 1540f07 + 430278e commit 670acf3

File tree

3 files changed

+177
-0
lines changed

3 files changed

+177
-0
lines changed

onelogin/saml2/__init__.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# coding: utf-8
2+
3+
"""
4+
SAML2 Authentication Module
5+
6+
This module provides compatibility for users expecting to import
7+
OneLogin_Saml2_Auth from onelogin.saml2.auth. However, OneLogin_Saml2_Auth is
8+
actually provided by the 'python3-saml' package, not this onelogin package.
9+
10+
This onelogin package is for OneLogin API management, while python3-saml is
11+
for SAML2 authentication integration.
12+
"""

onelogin/saml2/auth.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# coding: utf-8
2+
3+
"""
4+
SAML2 Authentication Module
5+
6+
This module provides compatibility for users expecting to import
7+
OneLogin_Saml2_Auth from onelogin.saml2.auth. However, OneLogin_Saml2_Auth is
8+
actually provided by the 'python3-saml' package, not this onelogin package.
9+
10+
This onelogin package is for OneLogin API management, while python3-saml is
11+
for SAML2 authentication integration.
12+
"""
13+
14+
import os
15+
import site
16+
17+
18+
def _check_python3_saml_installed():
19+
"""Check if python3-saml is installed by looking for its onelogin.saml2.auth module"""
20+
for site_dir in site.getsitepackages() + [site.getusersitepackages()]:
21+
if site_dir and os.path.exists(site_dir):
22+
potential_path = os.path.join(site_dir, 'onelogin', 'saml2', 'auth.py')
23+
if os.path.exists(potential_path):
24+
return True
25+
return False
26+
27+
28+
def __getattr__(name):
29+
"""
30+
Intercept attempts to import OneLogin_Saml2_Auth and provide helpful
31+
error message based on whether python3-saml is detected.
32+
"""
33+
if name == "OneLogin_Saml2_Auth":
34+
python3_saml_installed = _check_python3_saml_installed()
35+
36+
if python3_saml_installed:
37+
# python3-saml is installed but there's a namespace conflict
38+
raise ImportError(
39+
"OneLogin_Saml2_Auth is not available due to a package namespace conflict.\n\n"
40+
"You have both 'onelogin' (API management) and 'python3-saml' (SAML authentication) installed.\n"
41+
"These packages both provide 'onelogin' modules, causing a conflict.\n\n"
42+
"To fix this conflict:\n\n"
43+
"OPTION 1 - If you only need SAML authentication:\n"
44+
" 1. Uninstall this package: pip uninstall onelogin\n"
45+
" 2. Then import will work: from onelogin.saml2.auth import OneLogin_Saml2_Auth\n\n"
46+
"OPTION 2 - If you need both packages:\n"
47+
" 1. Import directly from python3-saml's location in site-packages\n"
48+
" 2. See: https://github.com/onelogin/python3-saml for documentation\n\n"
49+
"OPTION 3 - Use virtual environments to separate the packages\n\n"
50+
"To check your installations: pip list | grep -E '(onelogin|python3-saml)'"
51+
)
52+
else:
53+
# python3-saml is not installed
54+
raise ImportError(
55+
"OneLogin_Saml2_Auth is not available in this package.\n\n"
56+
"This package (onelogin) is for OneLogin API management.\n"
57+
"For SAML2 authentication, you need the 'python3-saml' package.\n\n"
58+
"To fix this:\n"
59+
"1. Install the correct package: pip install python3-saml\n"
60+
"2. Import from: from onelogin.saml2.auth import OneLogin_Saml2_Auth\n\n"
61+
"For more information: https://github.com/onelogin/python3-saml"
62+
)
63+
64+
raise AttributeError(
65+
f"module 'onelogin.saml2.auth' has no attribute '{name}'"
66+
)
67+
68+
69+
# Make module attributes available for inspection
70+
__all__ = []
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# coding: utf-8
2+
3+
"""
4+
Test import behavior for SAML2 compatibility module
5+
"""
6+
7+
import pytest
8+
import os
9+
import site
10+
11+
12+
class TestSaml2ImportCompatibility:
13+
"""Test the SAML2 import compatibility module"""
14+
15+
def test_import_onelogin_saml2_auth_raises_helpful_error(self):
16+
"""Test that importing OneLogin_Saml2_Auth raises a helpful error message"""
17+
18+
# Attempt to import OneLogin_Saml2_Auth
19+
with pytest.raises(ImportError) as exc_info:
20+
from onelogin.saml2.auth import OneLogin_Saml2_Auth
21+
22+
# Verify the error message contains helpful guidance
23+
error_message = str(exc_info.value)
24+
assert "OneLogin_Saml2_Auth is not available" in error_message
25+
assert "python3-saml" in error_message
26+
27+
# The specific message depends on whether python3-saml is detected
28+
python3_saml_installed = self._check_python3_saml_installed()
29+
30+
if python3_saml_installed:
31+
# Should show namespace conflict message
32+
assert "namespace conflict" in error_message
33+
assert "both 'onelogin'" in error_message
34+
assert "pip uninstall onelogin" in error_message
35+
else:
36+
# Should show installation instruction message
37+
assert "pip install python3-saml" in error_message
38+
assert "https://github.com/onelogin/python3-saml" in error_message
39+
40+
def test_getattr_for_nonexistent_attribute_raises_attribute_error(self):
41+
"""Test that accessing non-existent attributes raises AttributeError"""
42+
43+
import onelogin.saml2.auth
44+
with pytest.raises(AttributeError) as exc_info:
45+
getattr(onelogin.saml2.auth, 'NonExistentClass')
46+
47+
error_message = str(exc_info.value)
48+
assert "module 'onelogin.saml2.auth' has no attribute 'NonExistentClass'" in error_message
49+
50+
def test_saml2_module_can_be_imported(self):
51+
"""Test that the saml2 module itself can be imported without error"""
52+
53+
import onelogin.saml2
54+
# Should not raise any errors
55+
56+
def test_saml2_auth_module_can_be_imported(self):
57+
"""Test that the saml2.auth module itself can be imported without error"""
58+
59+
import onelogin.saml2.auth
60+
# Should not raise any errors
61+
62+
def test_saml2_auth_module_has_empty_all_list(self):
63+
"""Test that __all__ is defined and empty"""
64+
65+
import onelogin.saml2.auth
66+
assert hasattr(onelogin.saml2.auth, '__all__')
67+
assert onelogin.saml2.auth.__all__ == []
68+
69+
def test_python3_saml_detection_function(self):
70+
"""Test that the python3-saml detection function works correctly"""
71+
72+
import onelogin.saml2.auth
73+
result = onelogin.saml2.auth._check_python3_saml_installed()
74+
# Should return a boolean
75+
assert isinstance(result, bool)
76+
77+
# Test by checking if the expected path exists manually
78+
expected_exists = False
79+
for site_dir in site.getsitepackages() + [site.getusersitepackages()]:
80+
if site_dir and os.path.exists(site_dir):
81+
potential_path = os.path.join(site_dir, 'onelogin', 'saml2', 'auth.py')
82+
if os.path.exists(potential_path):
83+
expected_exists = True
84+
break
85+
86+
assert result == expected_exists
87+
88+
def _check_python3_saml_installed(self):
89+
"""Helper method to check if python3-saml is installed"""
90+
for site_dir in site.getsitepackages() + [site.getusersitepackages()]:
91+
if site_dir and os.path.exists(site_dir):
92+
potential_path = os.path.join(site_dir, 'onelogin', 'saml2', 'auth.py')
93+
if os.path.exists(potential_path):
94+
return True
95+
return False

0 commit comments

Comments
 (0)