在 Mac OS X、Windows 上使用 Python 获取 root 对话框?

发布于 2024-11-03 01:18:56 字数 152 浏览 0 评论 0原文

我将如何在我的 Python 应用程序中弹出权限提升对话框?我想要 Windows 上的 UAC 对话框和 Mac 上的密码身份验证对话框。

基本上,我的应用程序的一部分需要 root 权限,并且我需要通过 GUI 获得这些权限。我正在使用 wxPython。有什么想法吗?

How would I go about getting a privilege elevation dialog to pop up in my Python app? I want the UAC dialog on Windows and the password authentication dialog on Mac.

Basically, I need root privileges for part of my application and I need to get those privileges through the GUI. I'm using wxPython. Any ideas?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

断肠人 2024-11-10 01:18:56

在 Windows 上,如果不启动新进程,则无法获取 UAC 对话框,甚至无法使用 CreateProcess 启动该进程。

可以通过运行另一个具有适当清单文件的应用程序来启动 UAC 对话框 - 请参阅 在 Vista 中以管理员身份运行已编译的 python (py2exe),了解如何使用 py2exe 执行此操作的示例。

您还可以通过编程方式将 runas 动词与 win32 api ShellExecute http://msdn.microsoft.com/en-us/library/bb762153(v=vs.85).aspx - 您可以使用 ctypes http://python.net/crew/theller/ctypes/ 调用它它是 python 2.5+ iirc 上标准库的一部分。

抱歉不知道Mac。如果您提供更多关于您想要在 Windows 上完成的任务的详细信息,我也许能够提供更具体的帮助。

On Windows you cannot get the UAC dialog without starting a new process, and you cannot even start that process with CreateProcess.

The UAC dialog can be brought about by running another application that has the appropriate manifest file - see Running compiled python (py2exe) as administrator in Vista for an example of how to do this with py2exe.

You can also programatically use the runas verb with the win32 api ShellExecute http://msdn.microsoft.com/en-us/library/bb762153(v=vs.85).aspx - you can call this by using ctypes http://python.net/crew/theller/ctypes/ which is part of the standard library on python 2.5+ iirc.

Sorry don't know about Mac. If you give more detail on what you want to accomplish on Windows I might be able to provide more specific help.

静若繁花 2024-11-10 01:18:56

我知道这篇文章有点旧,但我写了以下内容作为我的问题的解决方案(在 Linux 和 OS X 上以 root 身份运行 python 脚本)。

我编写了以下 bash 脚本来以管理员权限执行 bash/python 脚本(适用于 Linux 和 OS X 系统):

#!/bin/bash

if [ -z "$1" ]; then
    echo "Specify executable"
    exit 1
fi

EXE=$1

available(){
    which $1 >/dev/null 2>&1
}

platform=`uname`

if [ "$platform" == "Darwin" ]; then
    MESSAGE="Please run $1 as root with sudo or install osascript (should be installed by default)"
else
    MESSAGE="Please run $1 as root with sudo or install gksu / kdesudo!"
fi

if [ `whoami` != "root" ]; then

    if [ "$platform" == "Darwin" ]; then
        # Apple
        if available osascript
        then
            SUDO=`which osascript`
        fi

    else # assume Linux
        # choose either gksudo or kdesudo
        # if both are avilable check whoch desktop is running
        if available gksudo
        then
            SUDO=`which gksudo`
        fi
        if available kdesudo
        then
            SUDO=`which kdesudo`
        fi
        if ( available gksudo && available kdesudo )
        then
            if [ $XDG_CURRENT_DESKTOP = "KDE" ]; then
                SUDO=`which kdesudo`;
            else
                SUDO=`which gksudo`
            fi
        fi

        # prefer polkit if available
        if available pkexec
        then
           SUDO=`which pkexec`
        fi

    fi

    if [ -z $SUDO ]; then
        if available zenity; then
            zenity --info --text "$MESSAGE"
            exit 0
        elif available notify-send; then
            notify-send "$MESSAGE"
            exit 0
        elif available xmessage notify-send; then
            xmessage -buttons Ok:0 "$MESSAGE"
            exit 0
        else
            echo "$MESSAGE"
        fi
    fi

fi

if [ "$platform" == "Darwin" ]
then
    $SUDO -e "do shell script \"$*\" with administrator privileges"
else
    $SUDO $@
fi

基本上,我设置系统的方式是在 bin 目录中保留子文件夹(例如 /usr/local /bin/pyscripts in /usr/local/bin),并创建指向可执行文件的符号链接。这对我来说有三个好处:

(1)如果我有不同的版本,我可以通过更改符号链接轻松切换执行哪个版本,并且它使 bin 目录保持干净(例如 /usr/local/bin/gcc-versions/4.9 /, /usr/local/bin/gcc-versions/4.8/, /usr/local/bin/gcc --> gcc-versions/4.8/gcc)

(2) 我可以存储脚本及其扩展名(对句法在 IDE 中突出显示),但可执行文件不包含它们,因为我喜欢这样(例如 svn-tools --> pyscripts/svn-tools.py)

(3) 我将在下面显示的原因:

我将脚本命名为“ run-as-root-wrapper”并将其放置在一个非常常见的路径中(例如/usr/local/bin),因此Python不需要任何特殊的东西来找到它。然后我有以下 run_command.py 模块:

import os
import sys
from distutils.spawn import find_executable

#===========================================================================#

def wrap_to_run_as_root(exe_install_path, true_command, expand_path = True):
    run_as_root_path = find_executable("run-as-root-wrapper")

    if(not run_as_root_path):
        return False
    else:
        if(os.path.exists(exe_install_path)):
            os.unlink(exe_install_path)

        if(expand_path):
            true_command = os.path.realpath(true_command)
            true_command = os.path.abspath(true_command)
            true_command = os.path.normpath(true_command)

        f = open(exe_install_path, 'w')
        f.write("#!/bin/bash\n\n")
        f.write(run_as_root_path + " " + true_command + " $@\n\n")
        f.close()
        os.chmod(exe_install_path, 0755)

        return True

在我的实际 python 脚本中,我有以下功能:

def install_cmd(args):
    exe_install_path = os.path.join(args.prefix, 
                                    os.path.join("bin", args.name))

    if(not run_command.wrap_to_run_as_root(exe_install_path, sys.argv[0])):
        os.symlink(os.path.realpath(sys.argv[0]), exe_install_path)

因此,如果我有一个名为 TrackingBlocker.py 的脚本(我用来修改 /etc/hosts 文件以重新路由已知的实际脚本)跟踪域到 127.0.0.1),当我调用“sudo /usr/local/bin/pyscripts/TrackingBlocker.py --prefix /usr/local --name ModifyTrackingBlocker install”时(通过 argparse 模块处理参数),它安装“/usr/local/bin/ModifyTrackingBlocker”,这是一个执行的 bash 脚本,

/usr/local/bin/run-as-root-wrapper /usr/local/bin/pyscripts/TrackingBlocker.py [args]

例如

ModifyTrackingBlocker add tracker.ads.com

执行:

/usr/local/bin/run-as-root-wrapper /usr/local/bin/pyscripts/TrackingBlocker.py add tracker.ads.com

然后显示获得权限所需的身份验证对话框,以添加:

127.0.0.1  tracker.ads.com

到我的主机文件(只能由超级用户写入)。

如果您想简化/修改它以仅以 root 身份运行某些命令,您可以简单地将其添加到您的脚本中(使用上面提到的必要导入+导入子进程):

def run_as_root(command, args, expand_path = True):
    run_as_root_path = find_executable("run-as-root-wrapper")

    if(not run_as_root_path):
        return 1
    else:
        if(expand_path):
            command = os.path.realpath(command)
            command = os.path.abspath(command)
            command = os.path.normpath(command)

        cmd = []
        cmd.append(run_as_root_path)
        cmd.append(command)
        cmd.extend(args)

        return subprocess.call(' '.join(cmd), shell=True)

使用上面的内容(在 run_command 模块中):

>>> ret = run_command.run_as_root("/usr/local/bin/pyscripts/TrackingBlocker.py", ["status", "display"])
>>> /etc/hosts is blocking approximately 16147 domains

I know the post is a little old, but I wrote the following as a solution to my problem (running a python script as root on both Linux and OS X).

I wrote the following bash-script to execute bash/python scripts with administrator privileges (works on Linux and OS X systems):

#!/bin/bash

if [ -z "$1" ]; then
    echo "Specify executable"
    exit 1
fi

EXE=$1

available(){
    which $1 >/dev/null 2>&1
}

platform=`uname`

if [ "$platform" == "Darwin" ]; then
    MESSAGE="Please run $1 as root with sudo or install osascript (should be installed by default)"
else
    MESSAGE="Please run $1 as root with sudo or install gksu / kdesudo!"
fi

if [ `whoami` != "root" ]; then

    if [ "$platform" == "Darwin" ]; then
        # Apple
        if available osascript
        then
            SUDO=`which osascript`
        fi

    else # assume Linux
        # choose either gksudo or kdesudo
        # if both are avilable check whoch desktop is running
        if available gksudo
        then
            SUDO=`which gksudo`
        fi
        if available kdesudo
        then
            SUDO=`which kdesudo`
        fi
        if ( available gksudo && available kdesudo )
        then
            if [ $XDG_CURRENT_DESKTOP = "KDE" ]; then
                SUDO=`which kdesudo`;
            else
                SUDO=`which gksudo`
            fi
        fi

        # prefer polkit if available
        if available pkexec
        then
           SUDO=`which pkexec`
        fi

    fi

    if [ -z $SUDO ]; then
        if available zenity; then
            zenity --info --text "$MESSAGE"
            exit 0
        elif available notify-send; then
            notify-send "$MESSAGE"
            exit 0
        elif available xmessage notify-send; then
            xmessage -buttons Ok:0 "$MESSAGE"
            exit 0
        else
            echo "$MESSAGE"
        fi
    fi

fi

if [ "$platform" == "Darwin" ]
then
    $SUDO -e "do shell script \"$*\" with administrator privileges"
else
    $SUDO $@
fi

Basically, the way I set up my system is that I keep subfolders inside the bin directories (e.g. /usr/local/bin/pyscripts in /usr/local/bin), and create symbolic links to the executables. This has three benefits for me:

(1) If I have different versions, I can easily switch which one is executed by changing the symbolic link and it keeps the bin directory cleaner (e.g. /usr/local/bin/gcc-versions/4.9/, /usr/local/bin/gcc-versions/4.8/, /usr/local/bin/gcc --> gcc-versions/4.8/gcc)

(2) I can store the scripts with their extension (helpful for syntax highlighting in IDEs), but the executables do not contain them because I like it that way (e.g. svn-tools --> pyscripts/svn-tools.py)

(3) The reason I will show below:

I name the script "run-as-root-wrapper" and place it in a very common path (e.g. /usr/local/bin) so python doesn't need anything special to locate it. Then I have the following run_command.py module:

import os
import sys
from distutils.spawn import find_executable

#===========================================================================#

def wrap_to_run_as_root(exe_install_path, true_command, expand_path = True):
    run_as_root_path = find_executable("run-as-root-wrapper")

    if(not run_as_root_path):
        return False
    else:
        if(os.path.exists(exe_install_path)):
            os.unlink(exe_install_path)

        if(expand_path):
            true_command = os.path.realpath(true_command)
            true_command = os.path.abspath(true_command)
            true_command = os.path.normpath(true_command)

        f = open(exe_install_path, 'w')
        f.write("#!/bin/bash\n\n")
        f.write(run_as_root_path + " " + true_command + " $@\n\n")
        f.close()
        os.chmod(exe_install_path, 0755)

        return True

In my actual python script, I have the following function:

def install_cmd(args):
    exe_install_path = os.path.join(args.prefix, 
                                    os.path.join("bin", args.name))

    if(not run_command.wrap_to_run_as_root(exe_install_path, sys.argv[0])):
        os.symlink(os.path.realpath(sys.argv[0]), exe_install_path)

So if I have a script called TrackingBlocker.py (actual script I use to modify the /etc/hosts file to re-route known tracking domains to 127.0.0.1), when I call "sudo /usr/local/bin/pyscripts/TrackingBlocker.py --prefix /usr/local --name ModifyTrackingBlocker install" (arguments handled via argparse module), it installs "/usr/local/bin/ModifyTrackingBlocker", which is a bash script executing

/usr/local/bin/run-as-root-wrapper /usr/local/bin/pyscripts/TrackingBlocker.py [args]

e.g.

ModifyTrackingBlocker add tracker.ads.com

executes:

/usr/local/bin/run-as-root-wrapper /usr/local/bin/pyscripts/TrackingBlocker.py add tracker.ads.com

which then displays the authentification dialog needed to get the privileges to add:

127.0.0.1  tracker.ads.com

to my hosts file (which is only writable by a superuser).

If you want to simplify/modify it to run only certain commands as root, you could simply add this to your script (with the necessary imports noted above + import subprocess):

def run_as_root(command, args, expand_path = True):
    run_as_root_path = find_executable("run-as-root-wrapper")

    if(not run_as_root_path):
        return 1
    else:
        if(expand_path):
            command = os.path.realpath(command)
            command = os.path.abspath(command)
            command = os.path.normpath(command)

        cmd = []
        cmd.append(run_as_root_path)
        cmd.append(command)
        cmd.extend(args)

        return subprocess.call(' '.join(cmd), shell=True)

Using the above (in run_command module):

>>> ret = run_command.run_as_root("/usr/local/bin/pyscripts/TrackingBlocker.py", ["status", "display"])
>>> /etc/hosts is blocking approximately 16147 domains
懷念過去 2024-11-10 01:18:56

我在 Mac OS X 上遇到了同样的问题。我有一个有效的解决方案,但它不是最佳的。我将在这里解释我的解决方案,并继续寻找更好的解决方案。

在程序开始时,我通过执行

def _elevate():
    """Elevate user permissions if needed"""
    if platform.system() == 'Darwin':
        try:
            os.setuid(0)
        except OSError:
            _mac_elevate()

os.setuid(0) 检查我是否是 root,如果我还不是 root,则会失败,这将触发 _mac_elevate() ,它会以管理员身份从头开始重新启动我的程序osascript 的帮助。 osascript 可用于执行 applescript 和其他东西。我这样使用它:

def _mac_elevate():
    """Relaunch asking for root privileges."""
    print "Relaunching with root permissions"
    applescript = ('do shell script "./my_program" '
                   'with administrator privileges')
    exit_code = subprocess.call(['osascript', '-e', applescript])
    sys.exit(exit_code)

问题是,如果我像上面那样使用 subprocess.call,我会保持当前进程运行,并且我的应用程序将有两个实例运行,并提供两个停靠图标。如果我使用 subprocess.Popen 并让非特权进程立即终止,我将无法使用退出代码,也无法获取 stdout/stderr 流并传播到启动原始进程的终端。

I'm having the same problem on Mac OS X. I have a working solution, but it's not optimal. I will explain my solution here and continue looking for a better one.

At the beginning of the program I check if I'm root or not by executing

def _elevate():
    """Elevate user permissions if needed"""
    if platform.system() == 'Darwin':
        try:
            os.setuid(0)
        except OSError:
            _mac_elevate()

os.setuid(0) will fail if i'm not already root and that will trigger _mac_elevate() which relaunch my program from the start as administrator with the help of osascript. osascript can be used to execute applescript and other stuff. I use it like this:

def _mac_elevate():
    """Relaunch asking for root privileges."""
    print "Relaunching with root permissions"
    applescript = ('do shell script "./my_program" '
                   'with administrator privileges')
    exit_code = subprocess.call(['osascript', '-e', applescript])
    sys.exit(exit_code)

The problem with this is if I use subprocess.call as above I keep the current process running and there will be two instances of my app running giving two dock icons. If I use subprocess.Popen instead and let the non-priviledged process die instantly I can't make use of the exit code, nor can I fetch the stdout/stderr streams and propagate to the terminal starting the original process.

何其悲哀 2024-11-10 01:18:56

将 osascript 与具有管理员权限的 osascript 一起使用实际上只是 Apple 脚本包装对 AuthorizationExecuteWithPrivileges()

但是您可以使用 ctypes 直接从 Python3 调用 AuthorizationExecuteWithPrivileges()

例如,以下父脚本 spawn_root.py (以非 root 用户身份运行)生成子进程 root_child.py (以 root 权限运行)。

系统将提示用户在操作系统 GUI 弹出窗口中输入密码。请注意,这在无头会话(例如通过 ssh)上不起作用。它必须在GUI 内运行(例如Terminal.app)。

在 MacOS 质询对话框中正确输入用户密码后,root_child.py 会执行系统软关闭,这需要 MacOS 上的 root 权限。

父级 (spawn_root.py)

#!/usr/bin/env python3

import sys, ctypes
import ctypes.util
from ctypes import byref

sec = ctypes.cdll.LoadLibrary(ctypes.util.find_library("Security"))

kAuthorizationFlagDefaults = 0
auth = ctypes.c_void_p()
r_auth = byref(auth)
sec.AuthorizationCreate(None,None,kAuthorizationFlagDefaults,r_auth)

exe = [sys.executable,"root_child.py"]
args = (ctypes.c_char_p * len(exe))()
for i,arg in enumerate(exe[1:]):
  args[i] = arg.encode('utf8')

io = ctypes.c_void_p()

sec.AuthorizationExecuteWithPrivileges(auth,exe[0].encode('utf8'),0,args,byref(io))

子级 (root_child.py)

#!/usr/bin/env python3
import os

if __name__ == "__main__":

    f = open( "root_child.out", "a" )

    try:
        os.system( "shutdown -h now" )
        f.write( "SUCCESS: I am root!\n" )
    except Exception as e:
        f.write( "ERROR: I am not root :'(" +str(e)+ "\n" )

    f.close()

安全说明

显然,任何时候以 root 身份运行某些东西时,都需要非常小心!

AuthorizationExecuteWithPrivileges() 已弃用,但它可以安全使用。但它也可能不安全地使用!

它基本上可以归结为:你真的知道你以root身份运行什么吗?如果您以 root 身份运行的脚本位于具有全局可写权限的临时目录中(正如许多 MacOS 应用程序安装程序历史上所做的那样),那么任何恶意进程都可以获得 root 访问权限。

要安全地以 root 身份执行进程:

  1. 确保要启动的进程的权限为 root:root 0400 (或只能由 root 写入)
  2. 指定要启动的进程的绝对路径,并且不要不允许对该路径进行任何恶意修改

来源

  1. https://github.com/cloudmatrix/esky/blob/master/esky/sudo/sudo_osx.py
  2. https://github.com/BusKill/buskill-app/issues/14
  3. https://www.jamf.com/blog/detecting-insecure -macos 上的应用程序更新/

Using osascript with with administrator privileges is actually just Apple Script wrapping a call to AuthorizationExecuteWithPrivileges().

But you can call AuthorizationExecuteWithPrivileges() directly from Python3 with ctypes.

For example, the following parent script spawn_root.py (run as a non-root user) spawns a child process root_child.py (run with root privileges).

The user will be prompted to enter their password in the OS GUI pop-up. Note that this will not work on a headless session (eg over ssh). It must be run inside the GUI (eg Terminal.app).

After entering the user's password into the MacOS challenge dialog correctly, root_child.py executes a soft shutdown of the system, which requires root permission on MacOS.

Parent (spawn_root.py)

#!/usr/bin/env python3

import sys, ctypes
import ctypes.util
from ctypes import byref

sec = ctypes.cdll.LoadLibrary(ctypes.util.find_library("Security"))

kAuthorizationFlagDefaults = 0
auth = ctypes.c_void_p()
r_auth = byref(auth)
sec.AuthorizationCreate(None,None,kAuthorizationFlagDefaults,r_auth)

exe = [sys.executable,"root_child.py"]
args = (ctypes.c_char_p * len(exe))()
for i,arg in enumerate(exe[1:]):
  args[i] = arg.encode('utf8')

io = ctypes.c_void_p()

sec.AuthorizationExecuteWithPrivileges(auth,exe[0].encode('utf8'),0,args,byref(io))

Child (root_child.py)

#!/usr/bin/env python3
import os

if __name__ == "__main__":

    f = open( "root_child.out", "a" )

    try:
        os.system( "shutdown -h now" )
        f.write( "SUCCESS: I am root!\n" )
    except Exception as e:
        f.write( "ERROR: I am not root :'(" +str(e)+ "\n" )

    f.close()

Security Note

Obviously, any time you run something as root, you need to be very careful!

AuthorizationExecuteWithPrivileges() is deprecated, but it can be used safely. But it can also be used unsafely!

It basically boils down to: do you actually know what you're running as root? If the script you're running as root is located in a Temp dir that has world-writeable permissions (as a lot of MacOS App installers have done historically), then any malicious process could gain root access.

To execute a process as root safely:

  1. Make sure that the permissions on the process-to-be-launched are root:root 0400 (or writeable only by root)
  2. Specify the absolute path to the process-to-be-launched, and don't allow any malicious modification of that path

Sources

  1. https://github.com/cloudmatrix/esky/blob/master/esky/sudo/sudo_osx.py
  2. https://github.com/BusKill/buskill-app/issues/14
  3. https://www.jamf.com/blog/detecting-insecure-application-updates-on-macos/
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文