Compare commits

...

8 Commits

Author SHA1 Message Date
Alexandre Detiste fe54b0ac3f prefer newer unittest.mock from the standad library 2 months ago
Alexandre Detiste 58235e3675 add Python3 compatibility 2 months ago
Alex Willmer 933477fcbe Begin 0.3.7dev 2 months ago
Alex Willmer 5d789faee5 Prepare 0.3.6 2 months ago
Alex Willmer b822f20007 ansible_mitogen: Handle AnsibleUnsafeText et al in Ansible >= 7
Follwing fixes in Ansible 7-9 for CVE-2023-5764 cating `AnsibleUnsafeBytes` &
`AnsibleUnsafeText` to `bytes()` or `str()` requires special handling. The
handling is Ansible specific, so it shouldn't go in the mitogen package but
rather the ansible_mitogen package.

`ansible_mitogen.utils.unsafe.cast()` is most like `mitogen.utils.cast()`.
During development it began as `ansible_mitogen.utils.unsafe.unwrap_var()`,
closer to an inverse of `ansible.utils.unsafe_procy.wrap_var()`. Future
enhancements may move in this direction.

refs #977, refs #1046

See also
- https://github.com/advisories/GHSA-7j69-qfc3-2fq9
- https://github.com/ansible/ansible/pull/82293
- https://github.com/mitogen-hq/mitogen/wiki/AnsibleUnsafe-notes
2 months ago
Alex Willmer 813f253d6b ansible_mitogen: Make ansible_mitogens.utils a package
Prep work for ansible_mitogen.utils.unsafe
2 months ago
Alex Willmer d7979c3597 mitogen: Raise TypeError on `mitogen.utils.cast(custom_str)` failures
If casting a string fails then raise a TypeError. This is potentially an API
breaking change; chosen as the lesser evil vs. allowing silent errors.

`cast()` relies on `bytes(obj)` & `str(obj)` returning the respective
supertype. That's no longer the case for `AnsibleUnsafeBytes` &
`AnsibleUnsafeText`; since fixes/mitigations for  CVE-2023-5764.

fixes #1046, refs #977

See also
- https://github.com/advisories/GHSA-7j69-qfc3-2fq9
- https://github.com/ansible/ansible/pull/82293
2 months ago
Orion Poplawski dfc3c7d516 ansible_mitogen: Add Ansible 7 support
Co-authored-by: Orion Poplawski <orion@nwra.com>
2 months ago

@ -27,15 +27,15 @@ jobs:
Loc_27_210:
tox.env: py27-mode_localhost-ansible2.10
Loc_312_6:
Loc_312_7:
python.version: '3.12'
tox.env: py312-mode_localhost-ansible6
tox.env: py312-mode_localhost-ansible7
Van_27_210:
tox.env: py27-mode_localhost-ansible2.10-strategy_linear
Van_312_6:
Van_312_7:
python.version: '3.12'
tox.env: py312-mode_localhost-ansible6-strategy_linear
tox.env: py312-mode_localhost-ansible7-strategy_linear
- job: Linux
pool:
@ -147,3 +147,6 @@ jobs:
Ans_312_6:
python.version: '3.12'
tox.env: py312-mode_ansible-ansible6
Ans_312_7:
python.version: '3.12'
tox.env: py312-mode_ansible-ansible7

@ -43,7 +43,6 @@ import ansible.errors
import ansible.plugins.connection
import mitogen.core
import mitogen.utils
import ansible_mitogen.mixins
import ansible_mitogen.parsing
@ -51,6 +50,7 @@ import ansible_mitogen.process
import ansible_mitogen.services
import ansible_mitogen.target
import ansible_mitogen.transport_config
import ansible_mitogen.utils.unsafe
LOG = logging.getLogger(__name__)
@ -797,7 +797,7 @@ class Connection(ansible.plugins.connection.ConnectionBase):
call_context=self.binding.get_service_context(),
service_name='ansible_mitogen.services.ContextService',
method_name='get',
stack=mitogen.utils.cast(list(stack)),
stack=ansible_mitogen.utils.unsafe.cast(list(stack)),
)
except mitogen.core.CallError:
LOG.warning('Connection failed; stack configuration was:\n%s',
@ -848,7 +848,7 @@ class Connection(ansible.plugins.connection.ConnectionBase):
inventory_name, stack = self._build_stack()
worker_model = ansible_mitogen.process.get_worker_model()
self.binding = worker_model.get_binding(
mitogen.utils.cast(inventory_name)
ansible_mitogen.utils.unsafe.cast(inventory_name)
)
self._connect_stack(stack)
@ -933,7 +933,7 @@ class Connection(ansible.plugins.connection.ConnectionBase):
call_context=binding.get_service_context(),
service_name='ansible_mitogen.services.ContextService',
method_name='reset',
stack=mitogen.utils.cast(list(stack)),
stack=ansible_mitogen.utils.unsafe.cast(list(stack)),
)
finally:
binding.close()
@ -1011,8 +1011,8 @@ class Connection(ansible.plugins.connection.ConnectionBase):
emulate_tty = (not in_data and sudoable)
rc, stdout, stderr = self.get_chain().call(
ansible_mitogen.target.exec_command,
cmd=mitogen.utils.cast(cmd),
in_data=mitogen.utils.cast(in_data),
cmd=ansible_mitogen.utils.unsafe.cast(cmd),
in_data=ansible_mitogen.utils.unsafe.cast(in_data),
chdir=mitogen_chdir or self.get_default_cwd(),
emulate_tty=emulate_tty,
)
@ -1039,7 +1039,7 @@ class Connection(ansible.plugins.connection.ConnectionBase):
ansible_mitogen.target.transfer_file(
context=self.context,
# in_path may be AnsibleUnicode
in_path=mitogen.utils.cast(in_path),
in_path=ansible_mitogen.utils.unsafe.cast(in_path),
out_path=out_path
)
@ -1057,7 +1057,7 @@ class Connection(ansible.plugins.connection.ConnectionBase):
"""
self.get_chain().call_no_reply(
ansible_mitogen.target.write_path,
mitogen.utils.cast(out_path),
ansible_mitogen.utils.unsafe.cast(out_path),
mitogen.core.Blob(data),
mode=mode,
utimes=utimes,
@ -1119,7 +1119,7 @@ class Connection(ansible.plugins.connection.ConnectionBase):
call_context=self.binding.get_service_context(),
service_name='mitogen.service.FileService',
method_name='register',
path=mitogen.utils.cast(in_path)
path=ansible_mitogen.utils.unsafe.cast(in_path)
)
# For now this must remain synchronous, as the action plug-in may have

@ -49,7 +49,7 @@ __all__ = [
ANSIBLE_VERSION_MIN = (2, 10)
ANSIBLE_VERSION_MAX = (2, 13)
ANSIBLE_VERSION_MAX = (2, 14)
NEW_VERSION_MSG = (
"Your Ansible version (%s) is too recent. The most recent version\n"

@ -50,12 +50,12 @@ import ansible.plugins.action
import mitogen.core
import mitogen.select
import mitogen.utils
import ansible_mitogen.connection
import ansible_mitogen.planner
import ansible_mitogen.target
import ansible_mitogen.utils
import ansible_mitogen.utils.unsafe
from ansible.module_utils._text import to_text
@ -187,7 +187,7 @@ class ActionModuleMixin(ansible.plugins.action.ActionBase):
LOG.debug('_remote_file_exists(%r)', path)
return self._connection.get_chain().call(
ansible_mitogen.target.file_exists,
mitogen.utils.cast(path)
ansible_mitogen.utils.unsafe.cast(path)
)
def _configure_module(self, module_name, module_args, task_vars=None):
@ -324,7 +324,7 @@ class ActionModuleMixin(ansible.plugins.action.ActionBase):
# ~root/.ansible -> /root/.ansible
return self._connection.get_chain(use_login=(not sudoable)).call(
os.path.expanduser,
mitogen.utils.cast(path),
ansible_mitogen.utils.unsafe.cast(path),
)
def get_task_timeout_secs(self):
@ -387,11 +387,11 @@ class ActionModuleMixin(ansible.plugins.action.ActionBase):
ansible_mitogen.planner.Invocation(
action=self,
connection=self._connection,
module_name=mitogen.core.to_text(module_name),
module_args=mitogen.utils.cast(module_args),
module_name=ansible_mitogen.utils.unsafe.cast(mitogen.core.to_text(module_name)),
module_args=ansible_mitogen.utils.unsafe.cast(module_args),
task_vars=task_vars,
templar=self._templar,
env=mitogen.utils.cast(env),
env=ansible_mitogen.utils.unsafe.cast(env),
wrap_async=wrap_async,
timeout_secs=self.get_task_timeout_secs(),
)

@ -52,10 +52,10 @@ import ansible.constants
import mitogen.core
import mitogen.service
import mitogen.utils
import ansible_mitogen.loaders
import ansible_mitogen.module_finder
import ansible_mitogen.target
import ansible_mitogen.utils.unsafe
LOG = logging.getLogger(__name__)
@ -91,7 +91,7 @@ def _get_candidate_temp_dirs():
remote_tmp = ansible.constants.DEFAULT_REMOTE_TMP
system_tmpdirs = ('/var/tmp', '/tmp')
return mitogen.utils.cast([remote_tmp] + list(system_tmpdirs))
return ansible_mitogen.utils.unsafe.cast([remote_tmp] + list(system_tmpdirs))
def key_from_dict(**kwargs):

@ -0,0 +1,79 @@
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import ansible
import ansible.utils.unsafe_proxy
import ansible_mitogen.utils
import mitogen
import mitogen.core
import mitogen.utils
__all__ = [
'cast',
]
def _cast_to_dict(obj): return {cast(k): cast(v) for k, v in obj.items()}
def _cast_to_list(obj): return [cast(v) for v in obj]
def _cast_unsafe(obj): return obj._strip_unsafe()
def _passthrough(obj): return obj
# A dispatch table to cast objects based on their exact type.
# This is an optimisation, reliable fallbacks are required (e.g. isinstance())
_CAST_DISPATCH = {
bytes: bytes,
dict: _cast_to_dict,
list: _cast_to_list,
tuple: _cast_to_list,
mitogen.core.UnicodeType: mitogen.core.UnicodeType,
}
_CAST_DISPATCH.update({t: _passthrough for t in mitogen.utils.PASSTHROUGH})
if hasattr(ansible.utils.unsafe_proxy.AnsibleUnsafeText, '_strip_unsafe'):
_CAST_DISPATCH.update({
ansible.utils.unsafe_proxy.AnsibleUnsafeBytes: _cast_unsafe,
ansible.utils.unsafe_proxy.AnsibleUnsafeText: _cast_unsafe,
ansible.utils.unsafe_proxy.NativeJinjaUnsafeText: _cast_unsafe,
})
elif ansible_mitogen.utils.ansible_version[:2] <= (2, 16):
_CAST_DISPATCH.update({
ansible.utils.unsafe_proxy.AnsibleUnsafeBytes: bytes,
ansible.utils.unsafe_proxy.AnsibleUnsafeText: mitogen.core.UnicodeType,
})
else:
mitogen_ver = '.'.join(str(v) for v in mitogen.__version__)
raise ImportError("Mitogen %s can't unwrap Ansible %s AnsibleUnsafe objects"
% (mitogen_ver, ansible.__version__))
def cast(obj):
"""
Return obj (or a copy) with subtypes of builtins cast to their supertype.
This is an enhanced version of :func:`mitogen.utils.cast`. In addition it
handles ``ansible.utils.unsafe_proxy.AnsibleUnsafeText`` and variants.
There are types handled by :func:`ansible.utils.unsafe_proxy.wrap_var()`
that this function currently does not handle (e.g. `set()`), or preserve
preserve (e.g. `tuple()`). Future enhancements may change this.
:param obj:
Object to undecorate.
:returns:
Undecorated object.
"""
# Fast path: obj is a known type, dispatch directly
try:
unwrapper = _CAST_DISPATCH[type(obj)]
except KeyError:
pass
else:
return unwrapper(obj)
# Slow path: obj is some unknown subclass
if isinstance(obj, dict): return _cast_to_dict(obj)
if isinstance(obj, (list, tuple)): return _cast_to_list(obj)
return mitogen.utils.cast(obj)

@ -150,7 +150,7 @@ Noteworthy Differences
- Ansible 2.10, 3, and 4; with Python 2.7, or 3.6-3.11
- Ansible 5; with Python 3.8-3.11
- Ansible 6; with Python 3.8-3.12
- Ansible 6 and 7; with Python 3.8-3.12
Verify your installation is running one of these versions by checking
``ansible --version`` output.

@ -17,10 +17,22 @@ Release Notes
To avail of fixes in an unreleased version, please download a ZIP file
`directly from GitHub <https://github.com/mitogen-hq/mitogen/>`_.
Unreleased
----------
v0.3.6 (2024-04-04)
-------------------
* :gh:issue:`974` Support Ansible 7
* :gh:issue:`1046` Raise :py:exc:`TypeError` in :func:`<mitogen.util.cast()>`
when casting a string subtype to `bytes()` or `str()` fails. This is
potentially an API breaking change. Failures previously passed silently.
* :gh:issue:`1046` Add :func:`<ansible_mitogen.util.cast()>`, to cast
:class:`ansible.utils.unsafe_proxy.AnsibleUnsafe` objects in Ansible 7+.
v0.3.5 (2024-03-17)
-------------------

@ -2,7 +2,7 @@ import sys
sys.path.append('.')
VERSION = '0.3.5'
VERSION = '0.3.6'
author = u'Network Genomics'
copyright = u'2021, the Mitogen authors'

@ -130,6 +130,7 @@ sponsorship and outstanding future-thinking of its early adopters.
<li>luto</li>
<li><a href="https://mayeu.me/">Mayeu a.k.a Matthieu Maury</a></li>
<li><a href="https://twitter.com/nathanhruby">@nathanhruby</a></li>
<li><a href="https://github.com/opoplawski">Orion Poplawski</a></li>
<li><a href="http://pageflows.com/">Ramy</a></li>
<li>Scott Vokes</li>
<li><a href="https://twitter.com/sirtux">Tom Eichhorn</a></li>

@ -35,7 +35,7 @@ be expected. On the slave, it is built dynamically during startup.
#: Library version as a tuple.
__version__ = (0, 3, 6, 'dev0')
__version__ = (0, 3, 7, 'dev')
#: This is :data:`False` in slave contexts. Previously it was used to prevent

@ -190,10 +190,13 @@ PASSTHROUGH = (
def cast(obj):
"""
Return obj (or a copy) with subtypes of builtins cast to their supertype.
Subtypes of those in :data:`PASSTHROUGH` are not modified.
Many tools love to subclass built-in types in order to implement useful
functionality, such as annotating the safety of a Unicode string, or adding
additional methods to a dict. However, cPickle loves to preserve those
subtypes during serialization, resulting in CallError during :meth:`call
additional methods to a dict. However :py:mod:`pickle` serializes these
exactly, leading to :exc:`mitogen.CallError` during :meth:`Context.call
<mitogen.parent.Context.call>` in the target when it tries to deserialize
the data.
@ -201,6 +204,9 @@ def cast(obj):
custom sub-types removed. The functionality is not default since the
resulting walk may be computationally expensive given a large enough graph.
Raises :py:exc:`TypeError` if an unknown subtype is encountered, or
casting does not return the desired supertype.
See :ref:`serialization-rules` for a list of supported types.
:param obj:
@ -215,8 +221,16 @@ def cast(obj):
if isinstance(obj, PASSTHROUGH):
return obj
if isinstance(obj, mitogen.core.UnicodeType):
return mitogen.core.UnicodeType(obj)
return _cast(obj, mitogen.core.UnicodeType)
if isinstance(obj, mitogen.core.BytesType):
return mitogen.core.BytesType(obj)
return _cast(obj, mitogen.core.BytesType)
raise TypeError("Cannot serialize: %r: %r" % (type(obj), obj))
def _cast(obj, desired_type):
result = desired_type(obj)
if type(result) is not desired_type:
raise TypeError("Cast of %r to %r failed, got %r"
% (type(obj), desired_type, type(result)))
return result

@ -6,6 +6,7 @@
# - apt-get source libpam0g
# - cd */po/
# - python ~/pogrep.py "Password: "
from __future__ import print_function
import sys
import shlex
@ -31,7 +32,7 @@ for path in glob.glob('*.po'):
if last_word == 'msgid' and word == 'msgstr':
if last_rest == sys.argv[1]:
thing = rest.rstrip(': ').decode('utf-8').lower().encode('utf-8').encode('base64').rstrip()
print ' %-60s # %s' % (repr(thing)+',', path)
print(' %-60s # %s' % (repr(thing)+',', path))
last_word = word
last_rest = rest

@ -25,7 +25,7 @@
sudoable: false
register: out
- assert:
that: out.result == '{{user_facts.ansible_facts.ansible_user_dir}}/foo'
that: out.result == user_facts.ansible_facts.ansible_user_dir ~ '/foo'
fail_msg: out={{out}}
- name: "Expand ~/foo with become active. ~ is become_user's home."
@ -48,7 +48,7 @@
sudoable: false
register: out
- assert:
that: out.result == '{{user_facts.ansible_facts.ansible_user_dir}}/foo'
that: out.result == user_facts.ansible_facts.ansible_user_dir ~ '/foo'
fail_msg: out={{out}}
- name: "Expanding $HOME/foo has no effect."
@ -72,7 +72,7 @@
sudoable: true
register: out
- assert:
that: out.result == '{{user_facts.ansible_facts.ansible_user_dir}}/foo'
that: out.result == user_facts.ansible_facts.ansible_user_dir ~ '/foo'
fail_msg: out={{out}}
- name: "sudoable; Expand ~/foo with become active. ~ is become_user's home."
@ -96,7 +96,7 @@
sudoable: true
register: out
- assert:
that: out.result == '{{user_facts.ansible_facts.ansible_user_dir}}/foo'
that: out.result == user_facts.ansible_facts.ansible_user_dir ~ '/foo'
fail_msg: out={{out}}
- name: "sudoable; Expanding $HOME/foo has no effect."

@ -32,10 +32,12 @@
- async_out.invocation.module_args.creates == None
- async_out.invocation.module_args.executable == None
- async_out.invocation.module_args.removes == None
# In Ansible 4 (ansible-core 2.11) the warn parameter is deprecated and defaults to false.
# It's scheduled for removal in ansible-core 2.13.
- (ansible_version.full is version("2.11", "<", strict=True) and async_out.invocation.module_args.warn == True)
# | Ansible <= 3 | ansible-core <= 2.10 | present | True |
# | Ansible 4 - 6 | ansible-core 2.11 - 2.13 | deprecated | False |
# | Ansible >= 7 | ansible-core >= 2.14 | absent | n/a |
- (ansible_version.full is version("2.14", ">=", strict=True) and async_out.invocation.module_args.warn is not defined)
or (ansible_version.full is version("2.11", ">=", strict=True) and async_out.invocation.module_args.warn == False)
or (async_out.invocation.module_args.warn == True)
- async_out.rc == 0
- async_out.start.startswith("20")
- async_out.stderr == "there"

@ -9,7 +9,7 @@
- command: sudo -n whoami
args:
warn: false
warn: "{{ False if ansible_version.full is version('2.10', '<=', strict=True) else omit }}"
ignore_errors: true
register: sudo_available

@ -27,7 +27,7 @@
localhost
args:
chdir: ../..
warn: false
warn: "{{ False if ansible_version.full is version('2.10', '<=', strict=True) else omit }}"
register: result
- assert:

@ -27,7 +27,7 @@
localhost
args:
chdir: ../..
warn: false
warn: "{{ False if ansible_version.full is version('2.10', '<=', strict=True) else omit }}"
register: result
- assert:

@ -2,7 +2,11 @@ from __future__ import absolute_import
import os
import tempfile
import mock
try:
from unittest import mock
except ImportError:
import mock
import ansible.errors
import ansible.playbook.play_context

@ -4,7 +4,10 @@ import subprocess
import tempfile
import unittest
import mock
try:
from unittest import mock
except ImportError:
import mock
import ansible_mitogen.target
import testlib

@ -0,0 +1,11 @@
import unittest
import ansible_mitogen.utils
class AnsibleVersionTest(unittest.TestCase):
def test_ansible_version(self):
self.assertIsInstance(ansible_mitogen.utils.ansible_version, tuple)
self.assertIsInstance(ansible_mitogen.utils.ansible_version[0], int)
self.assertIsInstance(ansible_mitogen.utils.ansible_version[1], int)
self.assertEqual(2, ansible_mitogen.utils.ansible_version[0])

@ -0,0 +1,92 @@
import unittest
from ansible.utils.unsafe_proxy import AnsibleUnsafeBytes
from ansible.utils.unsafe_proxy import AnsibleUnsafeText
from ansible.utils.unsafe_proxy import wrap_var
import ansible_mitogen.utils.unsafe
import mitogen.core
class Bytes(bytes): pass
class Dict(dict): pass
class List(list): pass
class Set(set): pass
class Text(mitogen.core.UnicodeType): pass
class Tuple(tuple): pass
class CastTest(unittest.TestCase):
def assertIsType(self, obj, cls, msg=None):
self.assertIs(type(obj), cls, msg)
def assertUnchanged(self, obj):
self.assertIs(ansible_mitogen.utils.unsafe.cast(obj), obj)
def assertCasts(self, obj, expected):
cast = ansible_mitogen.utils.unsafe.cast
self.assertEqual(cast(obj), expected)
self.assertIsType(cast(obj), type(expected))
def test_ansible_unsafe(self):
self.assertCasts(AnsibleUnsafeBytes(b'abc'), b'abc')
self.assertCasts(AnsibleUnsafeText(u'abc'), u'abc')
def test_passthrough(self):
self.assertUnchanged(0)
self.assertUnchanged(0.0)
self.assertUnchanged(False)
self.assertUnchanged(True)
self.assertUnchanged(None)
self.assertUnchanged(b'')
self.assertUnchanged(u'')
def test_builtins_roundtrip(self):
self.assertCasts(wrap_var(b''), b'')
self.assertCasts(wrap_var({}), {})
self.assertCasts(wrap_var([]), [])
self.assertCasts(wrap_var(u''), u'')
self.assertCasts(wrap_var(()), [])
def test_subtypes_roundtrip(self):
self.assertCasts(wrap_var(Bytes()), b'')
self.assertCasts(wrap_var(Dict()), {})
self.assertCasts(wrap_var(List()), [])
self.assertCasts(wrap_var(Text()), u'')
self.assertCasts(wrap_var(Tuple()), [])
def test_subtype_nested_dict(self):
obj = Dict(foo=Dict(bar=u'abc'))
wrapped = wrap_var(obj)
unwrapped = ansible_mitogen.utils.unsafe.cast(wrapped)
self.assertEqual(unwrapped, {'foo': {'bar': u'abc'}})
self.assertIsType(unwrapped, dict)
self.assertIsType(unwrapped['foo'], dict)
self.assertIsType(unwrapped['foo']['bar'], mitogen.core.UnicodeType)
def test_subtype_roundtrip_list(self):
# wrap_var() preserves sequence types, cast() does not (for now)
obj = List([List([u'abc'])])
wrapped = wrap_var(obj)
unwrapped = ansible_mitogen.utils.unsafe.cast(wrapped)
self.assertEqual(unwrapped, [[u'abc']])
self.assertIsType(unwrapped, list)
self.assertIsType(unwrapped[0], list)
self.assertIsType(unwrapped[0][0], mitogen.core.UnicodeType)
def test_subtype_roundtrip_tuple(self):
# wrap_var() preserves sequence types, cast() does not (for now)
obj = Tuple([Tuple([u'abc'])])
wrapped = wrap_var(obj)
unwrapped = ansible_mitogen.utils.unsafe.cast(wrapped)
self.assertEqual(unwrapped, [[u'abc']])
self.assertIsType(unwrapped, list)
self.assertIsType(unwrapped[0], list)
self.assertIsType(unwrapped[0][0], mitogen.core.UnicodeType)
def test_unknown_types_raise(self):
cast = ansible_mitogen.utils.unsafe.cast
self.assertRaises(TypeError, cast, set())
self.assertRaises(TypeError, cast, Set())
self.assertRaises(TypeError, cast, 4j)

@ -1,4 +1,7 @@
import mock
try:
from unittest import mock
except ImportError:
import mock
import testlib

@ -57,7 +57,7 @@
dnf: dnf clean all
command: "{{ clean_command[ansible_pkg_mgr] }}"
args:
warn: false
warn: "{{ False if ansible_version.full is version('2.10', '<=', strict=True) else omit }}"
- name: Clean up apt package lists
shell: rm -rf {{item}}/*

@ -4,7 +4,10 @@ import types
import zlib
import unittest
import mock
try:
from unittest import mock
except ImportError:
import mock
import mitogen.core
import mitogen.utils

@ -1,7 +1,10 @@
import errno
import select
import mock
try:
from unittest import mock
except ImportError:
import mock
import testlib
import mitogen.core

@ -1,8 +1,12 @@
import logging
import mock
import sys
import unittest
try:
from unittest import mock
except ImportError:
import mock
import testlib
import mitogen.core
import mitogen.master

@ -2,7 +2,10 @@ import sys
import struct
import unittest
import mock
try:
from unittest import mock
except ImportError:
import mock
import mitogen.core
import mitogen.master

@ -1,4 +1,7 @@
import mock
try:
from unittest import mock
except ImportError:
import mock
import mitogen.core

@ -6,7 +6,10 @@ import sys
import time
import unittest
import mock
try:
from unittest import mock
except ImportError:
import mock
import testlib
import mitogen.core

@ -1,4 +1,7 @@
import mock
try:
from unittest import mock
except ImportError:
import mock
import mitogen.core
import mitogen.parent

@ -1,7 +1,10 @@
import signal
import testlib
import mock
try:
from unittest import mock
except ImportError:
import mock
import mitogen.parent

@ -1,9 +1,13 @@
import mock
import textwrap
import subprocess
import sys
import unittest
try:
from unittest import mock
except ImportError:
import mock
import mitogen.master
import testlib

@ -1,4 +1,7 @@
import mock
try:
from unittest import mock
except ImportError:
import mock
import mitogen.core
import mitogen.parent

@ -44,6 +44,44 @@ class Unicode(mitogen.core.UnicodeType): pass
class Bytes(mitogen.core.BytesType): pass
class StubbornBytes(mitogen.core.BytesType):
"""
A binary string type that persists through `bytes(...)`.
Stand-in for `AnsibleUnsafeBytes()` in Ansible 7-9 (core 2.14-2.16), after
fixes/mitigations for CVE-2023-5764.
"""
if mitogen.core.PY3:
def __bytes__(self): return self
def __str__(self): return self.decode()
else:
def __str__(self): return self
def __unicode__(self): return self.decode()
def decode(self, encoding='utf-8', errors='strict'):
s = super(StubbornBytes).encode(encoding=encoding, errors=errors)
return StubbornText(s)
class StubbornText(mitogen.core.UnicodeType):
"""
A text string type that persists through `unicode(...)` or `str(...)`.
Stand-in for `AnsibleUnsafeText()` in Ansible 7-9 (core 2.14-2.16), after
following fixes/mitigations for CVE-2023-5764.
"""
if mitogen.core.PY3:
def __bytes__(self): return self.encode()
def __str__(self): return self
else:
def __str__(self): return self.encode()
def __unicode__(self): return self
def encode(self, encoding='utf-8', errors='strict'):
s = super(StubbornText).encode(encoding=encoding, errors=errors)
return StubbornBytes(s)
class CastTest(testlib.TestCase):
def test_dict(self):
self.assertEqual(type(mitogen.utils.cast({})), dict)
@ -91,6 +129,15 @@ class CastTest(testlib.TestCase):
self.assertEqual(type(mitogen.utils.cast(b(''))), mitogen.core.BytesType)
self.assertEqual(type(mitogen.utils.cast(Bytes())), mitogen.core.BytesType)
def test_stubborn_types_raise(self):
stubborn_bytes = StubbornBytes(b('abc'))
self.assertIs(stubborn_bytes, mitogen.core.BytesType(stubborn_bytes))
self.assertRaises(TypeError, mitogen.utils.cast, stubborn_bytes)
stubborn_text = StubbornText(u'abc')
self.assertIs(stubborn_text, mitogen.core.UnicodeType(stubborn_text))
self.assertRaises(TypeError, mitogen.utils.cast, stubborn_text)
def test_unknown(self):
self.assertRaises(TypeError, mitogen.utils.cast, set())
self.assertRaises(TypeError, mitogen.utils.cast, 4j)

@ -57,7 +57,7 @@ envlist =
init,
py{27,36}-mode_ansible-ansible{2.10,3,4},
py{311}-mode_ansible-ansible{2.10,3,4,5},
py{312}-mode_ansible-ansible{6},
py{312}-mode_ansible-ansible{6,7},
py{27,36,312}-mode_mitogen-distro_centos{6,7,8},
py{27,36,312}-mode_mitogen-distro_debian{9,10,11},
py{27,36,312}-mode_mitogen-distro_ubuntu{1604,1804,2004},
@ -83,6 +83,7 @@ deps =
ansible4: ansible==4.10.0
ansible5: ansible~=5.0
ansible6: ansible~=6.0
ansible7: ansible~=7.0
install_command =
python -m pip --no-python-version-warning --disable-pip-version-check install {opts} {packages}
commands_pre =
@ -118,8 +119,9 @@ setenv =
distro_ubuntu1804: DISTRO=ubuntu1804
distro_ubuntu2004: DISTRO=ubuntu2004
# Note the plural, only applicable to MODE=ansible
# Ansible 6 (ansible-core 2.13) requires Python >= 2.7 or >= 3.5 on targets
# Ansible >= 6 (ansible-core >= 2.13) require Python 2.7 or >= 3.5 on targets
ansible6: DISTROS=centos7 centos8 debian9 debian10 debian11 ubuntu1604 ubuntu1804 ubuntu2004
ansible7: DISTROS=centos7 centos8 debian9 debian10 debian11 ubuntu1604 ubuntu1804 ubuntu2004
distros_centos: DISTROS=centos6 centos7 centos8
distros_centos5: DISTROS=centos5
distros_centos6: DISTROS=centos6

Loading…
Cancel
Save