Qtの基礎 - 管理者権限

提供:MochiuWiki : SUSE, EC, PCB
ナビゲーションに移動 検索に移動

概要

PolKit (以前は、PolicyKitとして知られていた) は、非特権ユーザセッションと特権システムコンテキストの間のネゴシエータとして機能するアプリケーションフレームワークである。

PolKitは、ユーザ、グループ、名前により特定のアクションを制限する機能を持つ。
ユーザセッションのプロセスが、システムコンテキストでアクションを実行するごとに、PolKitはクエリされる。 所謂、ポリシーで指定された設定に基づいて、"yes"、"no"、"needs authentication"である可能性がある。

sudoのような古典的な権限承認プログラムとは異なり、PolKitはセッション全体に対してroot権限を与えるのではなく、各アクションに対してのみ権限を与える。


PolKit-Qt-1

PolKit-Qt-1とは

PolKit-Qt-1のライセンスは、LGPL-2.1に基づく。

Polkitは、Unix系システムでの権限管理と認可を制御するためのフレームワークであり、アプリケーションが特定のアクションを実行するために必要な権限を取得するための対話を提供する。

Polkit-Qt-1ライブラリは、PolkitとQtアプリケーションを簡単に統合するためのツールキットである。
PolKit-Qt-1は、Qtソフトウェアの開発者がPolKit APIを簡単に利用できるようにすることを目的としている。

また、QActionクラスとQAbstractButtonボタンの便利なラッパーもあり、これらのコンポーネントをPolKitと簡単に統合することもできる。

PolKit-Qt-1は、以前のPolKit-Qtを直接置き換えるものではなく、PolKit-Qtのバックエンドであった旧PolicyKit(バージョン 0.9以下)と後方互換性はない。
現在、旧PolicyKitはメンテナンスされていないため、PolKit-Qtは、PolKit-Qt-1またはKAuth(KDEフレームワーク)に移植することが推奨される。

Polkit-Qt-1ライブラリの主な特徴と機能を、以下に示す。

  • 権限管理の統合
    Qtベースのアプリケーションが簡単にPolkitを使用して権限管理を行うことができる。
  • シンプルなAPI
    Qtのシグナルとスロットを活用して、直感的で使いやすいAPIを提供する。
  • 非同期操作
    非同期に認証リクエストを処理するため、ユーザインターフェースがブロックされることを防ぐ。
  • セキュリティ
    Polkit-Qt-1を使用することにより、アプリケーションが必要な権限だけを要求し、ユーザのセキュリティを保護する。


PolKit-Qt-1は、以下に示す3つのライブラリに分かれている。

  • polkit-qt-core-1
    GUIを使用せず、アクションや認証を制御することができる。
    また、PolKitの権威に関する有用な情報を取得して、制御することができる。
    主に、Authorityクラスが含まれている。

  • polkit-qt-gui-1
    GUIアイテムをpolkitのアクションと簡単に関連付けることができる。
    いくつかのラッパークラスを通して、QActionクラスやQAbstractButtonクラスをPolKitアクションに関連付けることができ、
    それらのプロパティをPolKitの結果に応じて変更させることができる。
    Actionクラス、ActionButtonクラス、ActionButtonsクラスの各クラスが含まれている。

  • polkit-qt-agent-1
    非常にシンプルな方法により、独自のPolKit認証エージェントを記述することができる。


PolKit-Qt-1の内部仕様を知りたい場合は、PolKit-Qt-1の公式ドキュメントを参照すること。

※注意
Qtライブラリだけでなく、KDEフレームワークのようなQt拡張に依存するソフトウェアを開発する場合、KDEフレームワークのKAuthを使用することを推奨する。


PolKit-Qt-1のインストール

パッケージ管理システムからインストールする場合

sudo zypper install libpolkit-qt5-1-devel


ソースコードからインストールする場合

PolKit-Qt-1のビルドに必要なライブラリをインストールする。

# Qt 5を使用する場合
sudo zypper install pkg-config glib2-devel polkit-devel \
                    libQt5Core-devel libQt5Core-private-headers-devel libQt5Widgets-devel libQt5Widgets-private-headers-devel \
                    libQt5DBus-devel libQt5DBus-private-headers-devel libQt5Xml-devel libQt5Test-devel

# Qt 6を使用する場合
sudo zypper install pkg-config glib2-devel polkit-devel \
                    qt6-base-common-devel qt6-base-devel qt6-core-devel libQt5Core-private-headers-devel qt6-widgets-devel libQt5Widgets-private-headers-devel \
                    qt6-dbus-devel  libQt5DBus-private-headers-devel qt6-xml-devel qt6-test-devel


PolKit-Qt-1のGithubまたは公式のGitLabにアクセスして、ソースコードをダウンロードする。
ダウンロードしたファイルを解凍する。

tar xf polkit-qt-1-<バージョン>.tar.gz
cd polkit-qt-1-<バージョン>.tar.gz


または、git cloneコマンドを実行して、ソースコードをダウンロードする。

git clone https://invent.kde.org/libraries/polkit-qt-1.git
cd polkit-qt-1


PolKit-Qt-1をインストールする。

# Qt 5を使用する場合
cmake \
      -DCMAKE_C_COMPILER=<GCC 8以降のgccのパス> -DCMAKE_CXX_COMPILER=<G++ 8以降のg++のパス> \
      -DCMAKE_INSTALL_PREFIX=<PolKit-Qt-1のインストールディレクトリ> \
      -DCMAKE_BUILD_TYPE=Release \
      -DBUILD_EXAMPLES=ON        \
      -DBUILD_TEST=ON            \
      -DSYSCONF_INSTALL_DIR=<PolKit-Qt-1のインストールディレクトリ> \
      ..

# Qt 6を使用する場合
cmake \
      -DCMAKE_C_COMPILER=<GCC 8以降のgccのパス> -DCMAKE_CXX_COMPILER=<G++ 8以降のg++のパス> \
      -DCMAKE_INSTALL_PREFIX=<PolKit-Qt-1のインストールディレクトリ> \
      -DCMAKE_BUILD_TYPE=Release \
      -DQT_MAJOR_VERSION=6       \
      -DBUILD_EXAMPLES=ON        \
      -DBUILD_TEST=ON            \
      -DSYSCONF_INSTALL_DIR=<PolKit-Qt-1のインストールディレクトリ> \
      ..


make -j $(nproc)
make install


~/.profileファイル等に、環境変数を追記する。

vi ~/.profile


# ~/.profile

export LD_LIBRARY_PATH="/<PolKit-Qt-1のインストールディレクトリ>/lib64:$LD_LIBRARY_PATH"
export PKG_CONFIG_PATH="/<PolKit-Qt-1のインストールディレクトリ>/lib64/pkgconfig:${PKG_CONFIG_PATH}"


※注意
Qt Creator(Qt Creatorに付属しているQtライブラリ)とPolKit-Qt-1を使用してビルドした場合、生成された実行ファイルおよびライブラリはQt Creatorに付属しているQtライブラリを指すことになる。
つまり、Qt Creatorに付属しているQtライブラリを環境変数PKG_CONFIG_PATH等に追加しなければ生成されたファイルが動作しないということである。

この動作を変更する場合(生成された実行ファイルおよびライブラリを/usrディレクトリにあるQtライブラリを指すようにする場合)は、
パッケージ管理システムから、少なくとも、Qt5Core、Qt5GUI、Qt5DBusをインストールする必要がある。

sudo zypper install libQt5Core-devel libQt5Gui-devel libQt5DBus-devel \
                    libqt5-qtbase-common-devel vulkan-devel vulkan-headers  # 依存関係は自動的にインストールされる


次に、cmakeコマンド、または、パッケージ管理システムからインストールしたqmake-qt5を使用して、Qtプロジェクトをビルドする必要がある。

# qmakeコマンドを使用する場合
mkdir build && cd build
qmake-qt5 <Qtプロジェクトファイル名>.pro  または  qmake <Qtプロジェクトファイル名>.pro
make -j $(nproc)
make install

# cmakeコマンドを使用する場合
mkdir build && cd build
cmake ..
make -j $(nproc)
make install



PolKit-Qt-1を使用した開発例 1

以下の例では、ネットワーク設定の変更に対する権限を確認して、必要に応じてユーザに対話を求めている。

例えば、システム設定アプリケーションがシステムのネットワーク設定を変更しようとする場合、そのアクションには管理者権限が必要である。
Polkit-Qt-1ライブラリを使用することにより、この権限を要求する対話を簡単に実装できる。

 find_package(PolkitQt-1)
 
 # Qt 5を使用する場合
 target_link_libraries(<ターゲット名> PRIVATE
    PolkitQt5-1::Core
    PolkitQt5-1::Agent
 )
 
 # Qt 6を使用する場合
 target_link_libraries(<ターゲット名> PRIVATE
    PolkitQt6-1::Core
    PolkitQt6-1::Agent
 )


 #include <PolkitQt1/Authority>
 #include <PolkitQt1/Action>
 
 using namespace PolkitQt1;
 
 Authority::Result result = Authority::instance()->checkAuthorizationSync("org.example.modify_network",
                                                                          Authority::Subject::fromSystemBusName("org.freedesktop.DBus"),
                                                                          Authority::AllowUserInteraction);
 if (result == Authority::Yes) {
    // 権限が許可された場合の処理
 }
 else {
    // 権限が拒否された場合の処理
 }



PolKit-Qt-1を使用した開発例 2

開発例に挙げるソフトウェア

実行ファイルの画面にあるボタンを押下する時、任意のディレクトリにroot権限でテキストファイルを作成または書き込みするソフトウェアを例に挙げる。

開発するプロジェクトを以下に示す。

  • Sample
    主となるGUIソフトウェア
  • SampleHelper
    管理者権限でファイル操作を行うヘルパー実行ファイル
  • PolKitポリシーファイル
    ファイルの場所 : /usr/share/polkit-1/actions
    .policy拡張子
    ファイル名を、org.qt.policykit.examples.policyファイルとする。
  • D-Busインターフェースファイル
    ファイルの場所 : /usr/share/dbus-1/interfaces
    .xml拡張子
    このファイルは、/usr/share/dbus-1/interfacesディレクトリに配置しなくてもよい。
    ファイル名を、org.qt.policykit.examples.xmlファイルとする。
  • D-Busポリシーファイル
    サードパーティ製のソフトウェアは、歴史的に/etc/dbus-1/system.dディレクトリにファイルを配置していたが、現在は非推奨とされている
    そのため、/usr/share/dbus-1/system.dディレクトリに配置することが望ましい。

    この/etc/dbus-1/system.dディレクトリは、システム管理者のために予約されたものとして扱われるべきである。

    ファイルの場所 :
    優先順位 1 : /etc/dbus-1/system-local.conf (新規作成)
    優先順位 2 : /etc/dbus-1/system.d
    優先順位 3 : /usr/share/dbus-1/system.d
    .conf拡張子
    ファイル名を、org.qt.policykit.examples.confファイルとする。
  • D-Busサービスファイル(D-Busアクティベーションファイル)
    • ファイルの場所 (root権限等)
      /usr/share/dbus-1/system-services

    • ファイルの場所 (一般ユーザ権限等)
      /usr/share/dbus-1/services

    D-Busでは、D-Busサービスにアクセスすると自動的にソフトウェアが実行される仕組みが存在する。
    この機能を使用する場合は、/usr/share/dbus-1/servicesディレクトリにD-Busサービスファイルを作成する必要がある。
    これにより、D-Busインターフェースのメソッドにアクセスする前に、ヘルパー実行ファイルを手動で実行する必要がなくなる。

    # /usr/share/dbus-1/system-services/org.qt.policykit.examples.serviceファイル
    
    [D-BUS Service]
    Name=<D-Busサービス名>
    Exec=<ヘルパー実行ファイルのフルパス>
    User=root
    


ヘルパー実行ファイルの開発

 # SampleHelper.proファイル
 
 QT -= gui
 QT += core dbus
 
 CONFIG += c++17
 CONFIG -= app_bundle
 
 # PolKit-Qt-1 Install directory
 isEmpty(polqt_dir) {
    polqt_dir = /usr
 }
 
 # Add PolKit-Qt-1 Library & Header directory
 !isEmpty(polqt_dir) {
    LIBS += \
       -L$${polqt_dir}/lib64 -lpolkit-qt5-core-1 \
       -L$${polqt_dir}/lib64 -lpolkit-qt5-agent-1 \
       -L$${polqt_dir}/lib64 -lpolkit-qt5-gui-1
    INCLUDEPATH += \
       $${polqt_dir}/include
 }
 else {
    LIBS += \
       -lpolkit-qt5-core-1 \
       -lpolkit-qt5-agent-1 \
       -lpolkit-qt5-gui-1
 }
 
 SOURCES += \
        SampleHelper.cpp \
        SamplesAdaptor.cpp \
        main.cpp
 
 HEADERS += \
       SampleHelper.h \
       SamplesAdaptor.h
 
 # Config Install directory
 isEmpty(prefix) {
    prefix = $${PWD}/$${TARGET}/bin
 }
 
 target.path = $${prefix}
 INSTALLS += target


 // main.cppファイル
 
 #include <QCoreApplication>
 #include "SampleHelper.h"
 
 int main(int argc, char *argv[])
 {
    QCoreApplication a(argc, argv);
    SampleHelper helper(argc, argv);
 
    return a.exec();
 }


 // SampleHelper.hファイル
 
 #ifndef SAMPLE_HELPER_H
 #define SAMPLE_HELPER_H
 
 #include <QDBusContext>
 #include <QDBusMessage>
 #include <QCoreApplication>
 
 class SampleHelper : public QCoreApplication, protected QDBusContext
 {
    Q_OBJECT
    Q_CLASSINFO("D-Bus Interface", "org.qt.policykit.examples")
 public:
    SampleHelper(int &argc, char **argv);
    ~SampleHelper() override;
 
 public Q_SLOTS:
    bool write(const QString &action);
    bool writeValue(const QString &action);
 };
 
 #endif


 // SampleHelper.cppファイル
 
 #include "SampleHelper.h"
 #include "SamplesAdaptor.h"
 #include <polkit-qt5-1/polkitqt1-authority.h>
 #include <QDBusConnection>
 #include <QTimer>
 #include <QDebug>
 #include <QFile>
 
 #define MINUTE 60000
 
 using namespace PolkitQt1;
 
 SampleHelper::SampleHelper(int &argc, char **argv) : QCoreApplication(argc, argv)
 {
    qDebug() << "Creating Helper";
    (void) new ExamplesAdaptor(this);
 
    // Register the DBus service
    if (!QDBusConnection::systemBus().registerService("org.qt.policykit.examples"))
    {
       qDebug() << QDBusConnection::systemBus().lastError().message();;
       QTimer::singleShot(0, this, SLOT(quit()));
       return;
    }
 
    if (!QDBusConnection::systemBus().registerObject("/", this))
    {
       qDebug() << "unable to register service interface to dbus";
       QTimer::singleShot(0, this, SLOT(quit()));
       return;
    }
 
    // Normally you will set a timeout so your application can free some resources of the poor client machine
    QTimer::singleShot(MINUTE, this, SLOT(quit()));
 }
 
 SampleHelper::~SampleHelper()
 {
    qDebug() << "Destroying Helper";
 }
 
 bool SampleHelper::write(const QString &action)
 {
    // message().service() is the service name of the caller.
    // We can check if the caller is authorized to the following action.
    Authority::Result result;
    PolkitQt1::SystemBusNameSubject subject(message().service());
 
    result = Authority::instance()->checkAuthorizationSync("org.qt.policykit.examples.write", subject, Authority::AllowUserInteraction);
    if (result == Authority::Yes)
    {  // Caller is authorized so we can perform the action
       return writeValue(action);
    }
    else
    {  // Caller is not authorized so the action can't be performed
       return false;
    }
 }
 
 bool SampleHelper::writeValue(const QString &action)
 {
    // This action must be authorized first. It will set the implicit authorization for the Shout action by editing the .policy file.
    QFileInfo FileInfo("/opt/sample.txt");
 
    QFile File("/opt/sample.txt");
    if(!File.open(QIODevice::WriteOnly))
    {
       QString strErrMsg = "File(" + FileInfo.fileName() + ") Open Error: " + File.errorString();
       qDebug() << strErrMsg;
       return -1;
    }
 
    QTextStream OutStream(&File);
    OutStream << "foo bar";
 
    File.close();
 
    return true;
 }


ここで、SamplesAdaptor.cppファイル、および、SamplesAdaptor.hファイルは、Qt Creator付属のqdbusxml2cppコマンドを実行することにより自動生成される。
自動生成された上記の2つのファイルをインクルードすることにより、ヘルパー実行ファイルが完成する。

# mocファイルはインクルードしない場合
qdbusxml2cpp -a <生成するcppファイル名とヘッダファイル名> -c <自動生成するヘルパークラス名 (親クラス)> -i <対象となるクラスを記述しているヘッダファイル> -l <対象となるクラス名> <D-Busインターフェースファイルのパス>
例. qdbusxml2cpp -a SamplesAdaptor -c SamplesAdaptor -i SampleHelper.h -l SampleHelper org.qt.policykit.examples.xml

# mocファイルもインクルードする場合
qdbusxml2cpp -m -a <生成するcppファイル名とヘッダファイル名> -c <自動生成するヘルパークラス名 (親クラス)> -i <対象となるクラスを記述しているヘッダファイル> -l <対象となるクラス名> <D-Busインターフェースファイルのパス>
例. qdbusxml2cpp -m -a SamplesAdaptor -c SamplesAdaptor -i SampleHelper.h -l SampleHelper org.qt.policykit.examples.xml


自動生成されたヘルパーファイルを、Qtプロジェクト等に追加する。

最後に、cmakeコマンド、または、パッケージ管理システムからインストールしたqmake-qt5を使用して、Qtプロジェクトをビルドする。

# qmakeコマンドを使用する場合
mkdir build && cd build
qmake-qt5 <ヘルパープロジェクトファイル名>.pro  または  qmake <ヘルパープロジェクトファイル名>.pro
make -j $(nproc)
make install

# cmakeコマンドを使用する場合
mkdir build && cd build
cmake ..
make -j $(nproc)
make install