diff --git a/certbot/compat/misc.py b/certbot/compat/misc.py index 2f4ba0c6b..c20ff804a 100644 --- a/certbot/compat/misc.py +++ b/certbot/compat/misc.py @@ -2,42 +2,32 @@ This compat module handles various platform specific calls that do not fall into one particular category. """ -import ctypes +from __future__ import absolute_import + import errno +import os # pylint: disable=os-module-forbidden import select import stat import sys +try: + from win32com.shell import shell as shellwin32 # pylint: disable=import-error +except ImportError: # pragma: no cover + shellwin32 = None # type: ignore + from certbot import errors -from certbot.compat import os - -UNPRIVILEGED_SUBCOMMANDS_ALLOWED = [ - 'certificates', 'enhance', 'revoke', 'delete', - 'register', 'unregister', 'config_changes', 'plugins'] -def raise_for_non_administrative_windows_rights(subcommand): +def raise_for_non_administrative_windows_rights(): + # type: () -> None """ On Windows, raise if current shell does not have the administrative rights. Do nothing on Linux. - :param str subcommand: The subcommand (like 'certonly') passed to the certbot client. - - :raises .errors.Error: If the provided subcommand must be run on a shell with - administrative rights, and current shell does not have these rights. - + :raises .errors.Error: If the current shell does not have administrative rights on Windows. """ - # Why not simply try ctypes.windll.shell32.IsUserAnAdmin() and catch AttributeError ? - # Because windll exists only on a Windows runtime, and static code analysis engines - # do not like at all non existent objects when run from Linux (even if we handle properly - # all the cases in the code). - # So we access windll only by reflection to trick theses engines. - if hasattr(ctypes, 'windll') and subcommand not in UNPRIVILEGED_SUBCOMMANDS_ALLOWED: - windll = getattr(ctypes, 'windll') - if windll.shell32.IsUserAnAdmin() == 0: - raise errors.Error( - 'Error, "{0}" subcommand must be run on a shell with administrative rights.' - .format(subcommand)) + if shellwin32 and shellwin32.IsUserAnAdmin() == 0: # pragma: no cover + raise errors.Error('Error, certbot must be run on a shell with administrative rights.') def os_geteuid(): @@ -57,9 +47,9 @@ def os_geteuid(): def os_rename(src, dst): + # type: (str, str) -> None """ Rename a file to a destination path and handles situations where the destination exists. - :param str src: The current file path. :param str dst: The new file path. """ @@ -75,7 +65,7 @@ def os_rename(src, dst): # We should never go on this line. Either we are on Linux and os.rename has succeeded, # either we are on Windows, and only Python >= 3.4 is supported where os.replace is # available. - raise RuntimeError('Error: tried to run os_rename on Python < 3.3. ' + raise RuntimeError('Error: tried to run os.replace on Python < 3.3. ' 'Certbot supports only Python 3.4 >= on Windows.') getattr(os, 'replace')(src, dst) diff --git a/certbot/compat/os.py b/certbot/compat/os.py index 0112fbc73..6a18780ce 100644 --- a/certbot/compat/os.py +++ b/certbot/compat/os.py @@ -29,3 +29,14 @@ std_sys.modules[__name__ + '.path'] = path # Clean all remaining importables that are not from the core os module. del ourselves, std_os, std_sys + + +# Because of the blocking strategy on file handlers on Windows, rename to not behave as expected +# with POSIX systems: an exception will be raised if dst already exists. Hopefully there is +# os.replace on Windows for Python 3, that will do the same than on POSIX. Hopefully also, only +# Python 3 is supported for Certbot. So we can rely on os.rename on Linux, and os.replace +# on Windows. +def rename(*unused_args, **unused_kwargs): # pylint: disable=function-redefined + """Method os.rename() is forbidden""" + raise RuntimeError('Usage of os.rename() is forbidden. ' # pragma: no cover + 'Use certbot.compat.misc.os_rename() instead.') diff --git a/certbot/main.py b/certbot/main.py index 5365cd591..384699d7a 100644 --- a/certbot/main.py +++ b/certbot/main.py @@ -1359,7 +1359,7 @@ def main(cli_args=None): # On windows, shell without administrative right cannot create symlinks required by certbot. # So we check the rights before continuing. - misc.raise_for_non_administrative_windows_rights(config.verb) + misc.raise_for_non_administrative_windows_rights() try: log.post_arg_parse_setup(config) diff --git a/certbot/tests/compat/compat_test.py b/certbot/tests/compat/misc_test.py similarity index 89% rename from certbot/tests/compat/compat_test.py rename to certbot/tests/compat/misc_test.py index 832c557f6..b9f935de0 100644 --- a/certbot/tests/compat/compat_test.py +++ b/certbot/tests/compat/misc_test.py @@ -1,10 +1,10 @@ -"""Tests for certbot.compat.""" +"""Tests for certbot.compat.misc""" import certbot.tests.util as test_util from certbot.compat import misc from certbot.compat import os -class OsReplaceTest(test_util.TempDirTestCase): +class OsRenameTest(test_util.TempDirTestCase): """Test to ensure consistent behavior of os_rename method""" def test_os_rename_to_existing_file(self):