mirror of
https://github.com/ansible/ansible.git
synced 2025-11-30 23:16:08 +07:00
Hide the functionality of the _internal/_encryption/_crypt.py module behind an object so that we don't have code executed at import time.
150 lines
6.4 KiB
Python
150 lines
6.4 KiB
Python
from __future__ import annotations
|
|
|
|
import errno
|
|
import pytest
|
|
from pytest_mock import MockerFixture
|
|
|
|
from ansible._internal._encryption._crypt import _CryptLib, CryptFacade, _FAILURE_TOKENS
|
|
|
|
|
|
class TestCryptFacade:
|
|
|
|
def test_unsupported_platform(self, mocker: MockerFixture) -> None:
|
|
"""Test that unsupported platforms are skipped."""
|
|
mock_libs = (
|
|
_CryptLib('foo', include_platforms=frozenset({'fake_platform'})),
|
|
)
|
|
mocker.patch('ansible._internal._encryption._crypt._CRYPT_LIBS', mock_libs)
|
|
|
|
with pytest.raises(ImportError, match=r'Cannot find crypt implementation'):
|
|
CryptFacade()
|
|
|
|
def test_libc_fallback(self, mocker: MockerFixture) -> None:
|
|
"""Test that a library name of None will load the libc library."""
|
|
mock_libs = (
|
|
_CryptLib(None),
|
|
)
|
|
mocker.patch('ansible._internal._encryption._crypt._CRYPT_LIBS', mock_libs)
|
|
load_lib_mock = mocker.patch('ctypes.cdll.LoadLibrary')
|
|
|
|
crypt_facade = CryptFacade()
|
|
|
|
load_lib_mock.assert_called_once_with(None)
|
|
assert crypt_facade._crypt_name is None
|
|
|
|
def test_library_with_no_crypt_methods(self, mocker: MockerFixture) -> None:
|
|
"""Test that a library without crypt() and crypt_r() is skipped."""
|
|
mock_libs = (
|
|
_CryptLib(None),
|
|
)
|
|
|
|
class MockCDLL:
|
|
pass
|
|
|
|
mocker.patch('ansible._internal._encryption._crypt._CRYPT_LIBS', mock_libs)
|
|
mocker.patch('ctypes.cdll.LoadLibrary', return_value=MockCDLL())
|
|
|
|
with pytest.raises(ImportError, match=r'Cannot find crypt implementation'):
|
|
CryptFacade()
|
|
|
|
def test_library_with_no_crypt_r_or_crypt_gensalt_rn(self, mocker: MockerFixture) -> None:
|
|
"""Test that a library without crypt_r() or crypt_gensalt_rn() is prepped correctly."""
|
|
mock_libs = (
|
|
_CryptLib(None),
|
|
)
|
|
|
|
class MockCDLL:
|
|
|
|
class MockCrypt:
|
|
def __init__(self):
|
|
self.argtypes = None
|
|
self.restype = None
|
|
|
|
def __init__(self):
|
|
self.crypt = self.MockCrypt()
|
|
self.crypt_gensalt = self.MockCrypt()
|
|
|
|
mocker.patch('ansible._internal._encryption._crypt._CRYPT_LIBS', mock_libs)
|
|
mocker.patch('ctypes.cdll.LoadLibrary', return_value=MockCDLL())
|
|
|
|
crypt_facade = CryptFacade()
|
|
|
|
assert crypt_facade._crypt_impl is not None
|
|
assert crypt_facade._crypt_impl.argtypes is not None
|
|
assert crypt_facade._crypt_impl.restype is not None
|
|
assert crypt_facade._use_crypt_r is False
|
|
|
|
assert crypt_facade._crypt_gensalt_impl is not None
|
|
assert crypt_facade._crypt_gensalt_impl.argtypes is not None
|
|
assert crypt_facade._crypt_gensalt_impl.restype is not None
|
|
assert crypt_facade._use_crypt_gensalt_rn is False
|
|
assert crypt_facade.has_crypt_gensalt
|
|
|
|
def test_crypt_fail_errno(self, mocker: MockerFixture) -> None:
|
|
"""Test crypt() setting failure errno raises OSError."""
|
|
mocker.patch('ctypes.get_errno', return_value=errno.EBADFD)
|
|
crypt_facade = CryptFacade()
|
|
|
|
with pytest.raises(OSError, match=r'crypt failed:'):
|
|
crypt_facade.crypt(b"test", b"123")
|
|
|
|
def test_crypt_result_none(self, mocker: MockerFixture) -> None:
|
|
"""Test crypt() implementation returning None raises ValueError."""
|
|
crypt_facade = CryptFacade()
|
|
mocker.patch.object(crypt_facade, '_crypt_impl', return_value=None)
|
|
|
|
with pytest.raises(ValueError, match=r'crypt failed: invalid salt or unsupported algorithm'):
|
|
crypt_facade.crypt(b"test", b"123")
|
|
|
|
def test_crypt_result_failure(self, mocker: MockerFixture) -> None:
|
|
"""Test crypt() implementation returning failure token raises ValueError."""
|
|
crypt_facade = CryptFacade()
|
|
mocker.patch.object(crypt_facade, '_crypt_impl', return_value=list(_FAILURE_TOKENS)[0])
|
|
|
|
with pytest.raises(ValueError, match=r'crypt failed: invalid salt or unsupported algorithm'):
|
|
crypt_facade.crypt(b"test", b"123")
|
|
|
|
def test_crypt_gensalt_called_with_no_impl(self, mocker: MockerFixture) -> None:
|
|
"""Calling crypt_gensalt() without impl should raise NotImplementedError."""
|
|
crypt_facade = CryptFacade()
|
|
mock_prop = mocker.patch('ansible._internal._encryption._crypt.CryptFacade.has_crypt_gensalt', new_callable=mocker.PropertyMock)
|
|
mock_prop.return_value = False
|
|
|
|
with pytest.raises(NotImplementedError, match=r'crypt_gensalt not available \(requires libxcrypt\)'):
|
|
crypt_facade.crypt_gensalt(b"", 1, b"")
|
|
|
|
def test_crypt_gensalt(self, mocker: MockerFixture) -> None:
|
|
"""Test the NOT _use_crypt_gensalt_rn code path of crypt_gensalt()."""
|
|
crypt_facade = CryptFacade()
|
|
crypt_facade._use_crypt_gensalt_rn = False
|
|
mock_impl = mocker.patch.object(crypt_facade, '_crypt_gensalt_impl', return_value='')
|
|
|
|
crypt_facade.crypt_gensalt(b'', 1, b'')
|
|
mock_impl.assert_called_once_with(b'', 1, b'', 0)
|
|
|
|
def test_crypt_gensalt_fail_errno(self, mocker: MockerFixture) -> None:
|
|
"""Test crypt_gensalt() setting failure errno raises OSError."""
|
|
mocker.patch('ctypes.get_errno', return_value=errno.EBADFD)
|
|
crypt_facade = CryptFacade()
|
|
|
|
with pytest.raises(OSError, match=r'crypt_gensalt failed:'):
|
|
crypt_facade.crypt_gensalt(b'', 1, b'')
|
|
|
|
def test_crypt_gensalt_result_none(self, mocker: MockerFixture) -> None:
|
|
"""Test crypt_gensalt() implementation returning None raises ValueError."""
|
|
crypt_facade = CryptFacade()
|
|
mocker.patch.object(crypt_facade, '_crypt_gensalt_impl', return_value=None)
|
|
|
|
with pytest.raises(ValueError, match=r'crypt_gensalt failed: unable to generate salt'):
|
|
crypt_facade.crypt_gensalt(b'', 1, b'')
|
|
|
|
def test_crypt_gensalt_result_failure(self, mocker: MockerFixture) -> None:
|
|
"""Test crypt_gensalt() implementation returning failure token raises ValueError."""
|
|
crypt_facade = CryptFacade()
|
|
# Skip the _rn version as it modifies impl return value
|
|
crypt_facade._use_crypt_gensalt_rn = False
|
|
mocker.patch.object(crypt_facade, '_crypt_gensalt_impl', return_value=list(_FAILURE_TOKENS)[0])
|
|
|
|
with pytest.raises(ValueError, match=r'crypt_gensalt failed: invalid prefix or unsupported algorithm'):
|
|
crypt_facade.crypt_gensalt(b'', 1, b'')
|