Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions onelogin/saml2/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# coding: utf-8

"""
SAML2 Authentication Module

This module provides compatibility for users expecting to import
OneLogin_Saml2_Auth from onelogin.saml2.auth. However, OneLogin_Saml2_Auth is
actually provided by the 'python3-saml' package, not this onelogin package.

This onelogin package is for OneLogin API management, while python3-saml is
for SAML2 authentication integration.
"""
70 changes: 70 additions & 0 deletions onelogin/saml2/auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# coding: utf-8

"""
SAML2 Authentication Module

This module provides compatibility for users expecting to import
OneLogin_Saml2_Auth from onelogin.saml2.auth. However, OneLogin_Saml2_Auth is
actually provided by the 'python3-saml' package, not this onelogin package.

This onelogin package is for OneLogin API management, while python3-saml is
for SAML2 authentication integration.
"""

import os
import site


def _check_python3_saml_installed():
"""Check if python3-saml is installed by looking for its onelogin.saml2.auth module"""
for site_dir in site.getsitepackages() + [site.getusersitepackages()]:
if site_dir and os.path.exists(site_dir):
potential_path = os.path.join(site_dir, 'onelogin', 'saml2', 'auth.py')
if os.path.exists(potential_path):
return True
return False


def __getattr__(name):
"""
Intercept attempts to import OneLogin_Saml2_Auth and provide helpful
error message based on whether python3-saml is detected.
"""
if name == "OneLogin_Saml2_Auth":
python3_saml_installed = _check_python3_saml_installed()

if python3_saml_installed:
# python3-saml is installed but there's a namespace conflict
raise ImportError(
"OneLogin_Saml2_Auth is not available due to a package namespace conflict.\n\n"
"You have both 'onelogin' (API management) and 'python3-saml' (SAML authentication) installed.\n"
"These packages both provide 'onelogin' modules, causing a conflict.\n\n"
"To fix this conflict:\n\n"
"OPTION 1 - If you only need SAML authentication:\n"
" 1. Uninstall this package: pip uninstall onelogin\n"
" 2. Then import will work: from onelogin.saml2.auth import OneLogin_Saml2_Auth\n\n"
"OPTION 2 - If you need both packages:\n"
" 1. Import directly from python3-saml's location in site-packages\n"
" 2. See: https://github.com/onelogin/python3-saml for documentation\n\n"
"OPTION 3 - Use virtual environments to separate the packages\n\n"
"To check your installations: pip list | grep -E '(onelogin|python3-saml)'"
)
else:
# python3-saml is not installed
raise ImportError(
"OneLogin_Saml2_Auth is not available in this package.\n\n"
"This package (onelogin) is for OneLogin API management.\n"
"For SAML2 authentication, you need the 'python3-saml' package.\n\n"
"To fix this:\n"
"1. Install the correct package: pip install python3-saml\n"
"2. Import from: from onelogin.saml2.auth import OneLogin_Saml2_Auth\n\n"
"For more information: https://github.com/onelogin/python3-saml"
)

raise AttributeError(
f"module 'onelogin.saml2.auth' has no attribute '{name}'"
)


# Make module attributes available for inspection
__all__ = []
95 changes: 95 additions & 0 deletions test/test_saml2_import_compatibility.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# coding: utf-8

"""
Test import behavior for SAML2 compatibility module
"""

import pytest
import os
import site


class TestSaml2ImportCompatibility:
"""Test the SAML2 import compatibility module"""

def test_import_onelogin_saml2_auth_raises_helpful_error(self):
"""Test that importing OneLogin_Saml2_Auth raises a helpful error message"""

# Attempt to import OneLogin_Saml2_Auth
with pytest.raises(ImportError) as exc_info:
from onelogin.saml2.auth import OneLogin_Saml2_Auth

# Verify the error message contains helpful guidance
error_message = str(exc_info.value)
assert "OneLogin_Saml2_Auth is not available" in error_message
assert "python3-saml" in error_message

# The specific message depends on whether python3-saml is detected
python3_saml_installed = self._check_python3_saml_installed()

if python3_saml_installed:
# Should show namespace conflict message
assert "namespace conflict" in error_message
assert "both 'onelogin'" in error_message
assert "pip uninstall onelogin" in error_message
else:
# Should show installation instruction message
assert "pip install python3-saml" in error_message
assert "https://github.com/onelogin/python3-saml" in error_message

def test_getattr_for_nonexistent_attribute_raises_attribute_error(self):
"""Test that accessing non-existent attributes raises AttributeError"""

import onelogin.saml2.auth
with pytest.raises(AttributeError) as exc_info:
getattr(onelogin.saml2.auth, 'NonExistentClass')

error_message = str(exc_info.value)
assert "module 'onelogin.saml2.auth' has no attribute 'NonExistentClass'" in error_message

def test_saml2_module_can_be_imported(self):
"""Test that the saml2 module itself can be imported without error"""

import onelogin.saml2
# Should not raise any errors

def test_saml2_auth_module_can_be_imported(self):
"""Test that the saml2.auth module itself can be imported without error"""

import onelogin.saml2.auth
# Should not raise any errors

def test_saml2_auth_module_has_empty_all_list(self):
"""Test that __all__ is defined and empty"""

import onelogin.saml2.auth
assert hasattr(onelogin.saml2.auth, '__all__')
assert onelogin.saml2.auth.__all__ == []

def test_python3_saml_detection_function(self):
"""Test that the python3-saml detection function works correctly"""

import onelogin.saml2.auth
result = onelogin.saml2.auth._check_python3_saml_installed()
# Should return a boolean
assert isinstance(result, bool)

# Test by checking if the expected path exists manually
expected_exists = False
for site_dir in site.getsitepackages() + [site.getusersitepackages()]:
if site_dir and os.path.exists(site_dir):
potential_path = os.path.join(site_dir, 'onelogin', 'saml2', 'auth.py')
if os.path.exists(potential_path):
expected_exists = True
break

assert result == expected_exists

def _check_python3_saml_installed(self):
"""Helper method to check if python3-saml is installed"""
for site_dir in site.getsitepackages() + [site.getusersitepackages()]:
if site_dir and os.path.exists(site_dir):
potential_path = os.path.join(site_dir, 'onelogin', 'saml2', 'auth.py')
if os.path.exists(potential_path):
return True
return False