From c326589439a6314cb2836dcce4adfde4ccd7e9fc Mon Sep 17 00:00:00 2001 From: Dmitry Voronin Date: Thu, 10 Jan 2019 11:02:13 +0300 Subject: [PATCH] Add lxqt-admin-user-password - external gui utility for changing password --- CMakeLists.txt | 1 + lxqt-admin-user-password/CMakeLists.txt | 54 ++++ .../changepassworddialog.cpp | 254 ++++++++++++++++++ .../changepassworddialog.h | 54 ++++ .../changepassworddialog.ui | 67 +++++ lxqt-admin-user-password/main.cpp | 71 +++++ .../translations/lxqt-admin-user-password.ts | 60 +++++ lxqt-admin-user/CMakeLists.txt | 5 + .../lxqt-admin-user-helper.default | 6 + lxqt-admin-user/mainwindow.cpp | 96 +++---- lxqt-admin-user/mainwindow.h | 5 +- lxqt-admin-user/misc.cpp | 64 +++++ lxqt-admin-user/misc.h | 10 + lxqt-admin-user/processworker.cpp | 45 ++++ lxqt-admin-user/processworker.h | 53 ++++ lxqt-admin-user/taskmanager.cpp | 84 ++++++ lxqt-admin-user/taskmanager.h | 43 +++ lxqt-admin-user/usermanager.cpp | 60 +---- lxqt-admin-user/usermanager.h | 4 +- 19 files changed, 932 insertions(+), 104 deletions(-) create mode 100644 lxqt-admin-user-password/CMakeLists.txt create mode 100644 lxqt-admin-user-password/changepassworddialog.cpp create mode 100644 lxqt-admin-user-password/changepassworddialog.h create mode 100644 lxqt-admin-user-password/changepassworddialog.ui create mode 100644 lxqt-admin-user-password/main.cpp create mode 100644 lxqt-admin-user-password/translations/lxqt-admin-user-password.ts create mode 100644 lxqt-admin-user/misc.cpp create mode 100644 lxqt-admin-user/misc.h create mode 100644 lxqt-admin-user/processworker.cpp create mode 100644 lxqt-admin-user/processworker.h create mode 100644 lxqt-admin-user/taskmanager.cpp create mode 100644 lxqt-admin-user/taskmanager.h diff --git a/CMakeLists.txt b/CMakeLists.txt index aedd1da6..c52b8c6b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,5 +42,6 @@ if(${CMAKE_SYSTEM_NAME} MATCHES "OpenBSD" OR ${CMAKE_SYSTEM_NAME} MATCHES "NetBS message(WARNING "${CMAKE_SYSTEM_NAME} is not supported by lxqt-admin-time") else() add_subdirectory(lxqt-admin-user) + add_subdirectory(lxqt-admin-user-password) add_subdirectory(lxqt-admin-time) endif() diff --git a/lxqt-admin-user-password/CMakeLists.txt b/lxqt-admin-user-password/CMakeLists.txt new file mode 100644 index 00000000..992955e4 --- /dev/null +++ b/lxqt-admin-user-password/CMakeLists.txt @@ -0,0 +1,54 @@ +project(lxqt-admin-user-password) + +# build static helper class first +include_directories ( + ${CMAKE_CURRENT_BINARY_DIR} +) + +# define sources +file(GLOB HEADERS *.h) +file(GLOB UI_FORMS *.ui) +file(GLOB SOURCES *.cpp) + +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(ALL_SOURCES + ${SOURCES} + ${UI_FORMS} + ${HEADERS} +) + +add_definitions(-DPROJECT_NAME="${PROJECT_NAME}") + +# Translations ********************************** +lxqt_translate_ts(muser_QM_FILES + UPDATE_TRANSLATIONS YES + UPDATE OPTIONS + SOURCES + ${MOCS} + ${SOURCES} + ${UI_FORMS} + INSTALL_DIR + "${LXQT_TRANSLATIONS_DIR}/${PROJECT_NAME}" + PULL_TRANSLATIONS NO +) + +lxqt_app_translation_loader(lxqt-admin-user-password_QM_LOADER ${PROJECT_NAME}) +#************************************************ + +add_executable(lxqt-admin-user-password + ${ALL_SOURCES} + ${lxqt-admin-user-password_QM_FILES} + ${lxqt-admin-user-password_QM_LOADER} +) + +target_link_libraries(lxqt-admin-user-password + KF5::WindowSystem + Qt5::Widgets + lxqt + pam +) + +install(TARGETS lxqt-admin-user-password RUNTIME DESTINATION bin) diff --git a/lxqt-admin-user-password/changepassworddialog.cpp b/lxqt-admin-user-password/changepassworddialog.cpp new file mode 100644 index 00000000..02d1aaeb --- /dev/null +++ b/lxqt-admin-user-password/changepassworddialog.cpp @@ -0,0 +1,254 @@ +#include "changepassworddialog.h" +#include "ui_changepassworddialog.h" + +#include + +#include +#include +#include + +#include + +int ChangePasswordDialog::conversation(int num_msg, + const struct pam_message **msg, + struct pam_response **resp, + void *appdata_ptr) +{ + *resp = 0; + ChangePasswordDialog *dialog = 0; + if (!(num_msg && (dialog = (ChangePasswordDialog *) appdata_ptr) && ! dialog->_userAbort)) + return PAM_CONV_ERR; + + int answerSize = num_msg * sizeof(struct pam_response); + struct pam_response *answer = 0; + + if (!(answer = (struct pam_response *) malloc(answerSize))) + return PAM_BUF_ERR; + + memset(answer, 0, answerSize); + + bool labelAdded = false; + for (int i = 0; i < num_msg; i++) + { + QString message = QString::fromLocal8Bit(msg[i]->msg); + switch (msg[i]->msg_style) + { + case PAM_PROMPT_ECHO_OFF: + { + labelAdded = true; + dialog->addLabel(message, true); + } + break; + case PAM_PROMPT_ECHO_ON: + { + labelAdded = true; + dialog->addLabel(message, false); + } + break; + case PAM_ERROR_MSG: + dialog->addWarning(message); + break; + case PAM_TEXT_INFO : + dialog->addMessage(message); + break; + default: + { + free(answer); + return PAM_CONV_ERR; + } + } + } + + if (!labelAdded) + { + *resp = answer; + return PAM_SUCCESS; + } + + dialog->showWarnings(); + dialog->showMessages(); + + if (!(dialog->exec() == QDialog::Accepted)) + { + dialog->_userAbort = true; + free(answer); + return PAM_CONV_ERR; + } + + for (int i = 0, j = 0; i < num_msg; i++) + { + switch (msg[i]->msg_style) + { + case PAM_PROMPT_ECHO_OFF: + case PAM_PROMPT_ECHO_ON: + { + char *str = strdup(qPrintable(dialog->values().at(j))); + j++; + if (!str) + { + for (int k = 0; k < i; k++) + free(answer[k].resp); + free(answer); + return PAM_BUF_ERR; + } + answer[i].resp = str; + } + break; + default : + break; + } + } + + *resp = answer; + return PAM_SUCCESS; +} + +ChangePasswordDialog::ChangePasswordDialog(QWidget *parent) +: QDialog(parent) +, _ui(new Ui::ChangePasswordDialog) +, _userAbort(false) +{ + _ui->setupUi(this); +} + +ChangePasswordDialog::~ChangePasswordDialog() +{ + delete _ui; +} + +const QString &ChangePasswordDialog::user() const +{ + return _user; +} + +void ChangePasswordDialog::setUser(const QString &user) +{ + if (_user == user) + return; + + _user = user; + setWindowTitle(tr("Change password for user \"%1\"").arg(_user)); +} + +void ChangePasswordDialog::addWarning(const QString &warning) +{ + _warnings.append(warning); +} + +void ChangePasswordDialog::addMessage(const QString &message) +{ + _messages.append(message); +} + +void ChangePasswordDialog::addLabel(const QString &textLabel, bool isShadow) +{ + QLabel *label = new QLabel();; + QLineEdit *lineEdit = new QLineEdit(); + + label->setText(textLabel); + lineEdit->setEchoMode(isShadow ? QLineEdit::Password : QLineEdit::Normal); + + _ui->_verticalLayout->addWidget(label); + _ui->_verticalLayout->addWidget(lineEdit); +} + +const QList &ChangePasswordDialog::values() const +{ + return _values; +} + +bool ChangePasswordDialog::changePassword() +{ + if (_user.isEmpty()) + return false; + + _userAbort = false; + + pam_handle_t *pamh = NULL; + + pam_conv pam_conversation; + pam_conversation.conv = ChangePasswordDialog::conversation; + pam_conversation.appdata_ptr = (void *) this; + + int result = 0; + + result = pam_start("password", qPrintable(_user), &pam_conversation, &pamh); + + if (result != PAM_SUCCESS) + { + QMessageBox::critical(parentWidget(), tr("PAM transaction"), tr("PAM transaction not started.\nError code: %1").arg(result)); + return false; + } + + bool isChanged = ((result = pam_chauthtok(pamh, 0)) == PAM_SUCCESS); + + showWarnings(); + showMessages(); + + if ((result = pam_end(pamh, result)) != PAM_SUCCESS) + QMessageBox::critical(parentWidget(), tr("PAM transaction"), tr("PAM transaction not finished.\nError code: %1").arg(result)); + + return isChanged; +} + +void ChangePasswordDialog::reset() +{ + int size = _ui->_verticalLayout->count(); + for (int i = 0; i < size; i++) + { + QLayoutItem *layoutItem = _ui->_verticalLayout->itemAt(0); + QWidget *widget = layoutItem->widget(); + _ui->_verticalLayout->removeItem(layoutItem); + if (widget) + delete widget; + } +} + +void ChangePasswordDialog::showMessages() +{ + if (_messages.size()) + { + QMessageBox::information(parentWidget(), windowTitle(), _messages.join(QStringLiteral("\n"))); + _messages.clear(); + } +} + +void ChangePasswordDialog::showWarnings() +{ + if (_warnings.size()) + { + QMessageBox::warning(parentWidget(), windowTitle(), _warnings.join(QStringLiteral("\n"))); + _warnings.clear(); + } +} + +void ChangePasswordDialog::showEvent(QShowEvent *event) +{ + if (!_ui->_verticalLayout->count()) + return; + _ui->_verticalLayout->addStretch(1); + QDialog::showEvent(event); + _ui->_verticalLayout->itemAt(1)->widget()->setFocus(); + +} + +void ChangePasswordDialog::accept() +{ + _values.clear(); + + int size = _ui->_verticalLayout->count(); + for (int i = 0; (i + 1) < size; i += 2) + { + QLineEdit *lineEdit = qobject_cast(_ui->_verticalLayout->itemAt(i + 1)->widget()); + if (lineEdit) + _values.append( lineEdit->text() ); + } + reset(); + QDialog::accept(); +} + +void ChangePasswordDialog::reject() +{ + reset(); + QDialog::reject(); +} diff --git a/lxqt-admin-user-password/changepassworddialog.h b/lxqt-admin-user-password/changepassworddialog.h new file mode 100644 index 00000000..4ff75962 --- /dev/null +++ b/lxqt-admin-user-password/changepassworddialog.h @@ -0,0 +1,54 @@ +#ifndef CHANGEPASSWORDDIALOG_H +#define CHANGEPASSWORDDIALOG_H + +#include + +namespace Ui { +class ChangePasswordDialog; +} + +class ChangePasswordDialog : public QDialog +{ + Q_OBJECT + +public: + static int conversation(int, const struct pam_message**, struct pam_response**, void *); + + ChangePasswordDialog(QWidget */*parent*/ = 0); + virtual ~ChangePasswordDialog(); + + const QString &user() const; + void setUser(const QString &user); + + void addWarning(const QString &); + void addMessage(const QString &); + void addLabel(const QString &, bool); + + const QList &values() const; + bool changePassword(); + +protected: + void reset(); + void showMessages(); + void showWarnings(); + + virtual void showEvent(QShowEvent *event); + +public slots: + virtual void accept(); + virtual void reject(); + +protected: + Ui::ChangePasswordDialog *_ui; + + bool _userAbort; + + QString _user; + + QStringList _warnings; + QStringList _messages; + QList _shadows; + QList _values; +}; + +#endif // CHANGEPASSWORDDIALOG_H diff --git a/lxqt-admin-user-password/changepassworddialog.ui b/lxqt-admin-user-password/changepassworddialog.ui new file mode 100644 index 00000000..bbfdc206 --- /dev/null +++ b/lxqt-admin-user-password/changepassworddialog.ui @@ -0,0 +1,67 @@ + + + ChangePasswordDialog + + + + 0 + 0 + 300 + 67 + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + + + _buttonBox + accepted() + ChangePasswordDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + _buttonBox + rejected() + ChangePasswordDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/lxqt-admin-user-password/main.cpp b/lxqt-admin-user-password/main.cpp new file mode 100644 index 00000000..0ef40905 --- /dev/null +++ b/lxqt-admin-user-password/main.cpp @@ -0,0 +1,71 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "changepassworddialog.h" + +int main(int argc, char **argv) +{ + QApplication::setSetuidAllowed(true); + LXQt::Application app(argc, argv, true); + app.setQuitOnLastWindowClosed(false); + + QApplication::setApplicationName(QSL(PROJECT_NAME)); + QCommandLineParser parser; + QString user; + + parser.setApplicationDescription(QObject::tr("LXQt helper for password changing via PAM stack")); + parser.addHelpOption(); + + QCommandLineOption userOption(QStringList() << QSL("u") << QSL("user"), + QObject::tr("User name for password changing"), + QSL("user")); + parser.addOption(userOption); + + parser.process(app); + + if (parser.isSet(userOption)) + { + user = parser.value(userOption); + + struct passwd *pw = getpwnam(qPrintable(user)); + if (!pw) + { + QMessageBox::critical(nullptr, + QSL(PROJECT_NAME), + QObject::tr("User %1 does not exists").arg(user)); + lxqtApp->quit(); + return 1; + } + if (getuid() && (pw->pw_uid != getuid())) + { + QMessageBox::critical(nullptr, + QSL(PROJECT_NAME), + QObject::tr("You may not view or modify password information for %1").arg(user)); + lxqtApp->quit(); + return 1; + } + } + else + user = QString::fromLocal8Bit(qgetenv("USER")); + + ChangePasswordDialog dialog; + dialog.setWindowIcon(QIcon::fromTheme(QSL("preferences-system"))); + dialog.setUser(user); + + bool result = dialog.changePassword(); + + if (!result) + { + QTextStream(stderr) << QObject::tr("Password does not changed") << endl; + lxqtApp->quit(); + return 1; + } + + return 0; +} diff --git a/lxqt-admin-user-password/translations/lxqt-admin-user-password.ts b/lxqt-admin-user-password/translations/lxqt-admin-user-password.ts new file mode 100644 index 00000000..cb6fdf3a --- /dev/null +++ b/lxqt-admin-user-password/translations/lxqt-admin-user-password.ts @@ -0,0 +1,60 @@ + + + + + ChangePasswordDialog + + + Change password for user "%1" + Смена пароля для пользователя "%1" + + + + + PAM transaction + Транзакция PAM + + + + PAM transaction not started. +Error code: %1 + Транзакция PAM не стартовала. +Код ошибки: %1 + + + + PAM transaction not finished. +Error code: %1 + Транзакция PAM не завершена. +Код ошибки: %1 + + + + QObject + + + LXQt helper for password changing via PAM stack + Графическое приложение LXQt для смены пароля через PAM стек + + + + User name for password changing + Логин пользователя для смены пароля + + + + User %1 does not exists + Пользователь %1 не существует + + + + You may not view or modify password information for %1 + Вы не можете получить или изменить информацию о пароле пользователя %1 + + + + Password does not changed + Пароль не изменен + + + diff --git a/lxqt-admin-user/CMakeLists.txt b/lxqt-admin-user/CMakeLists.txt index 5c9c9e77..2252291d 100644 --- a/lxqt-admin-user/CMakeLists.txt +++ b/lxqt-admin-user/CMakeLists.txt @@ -11,6 +11,9 @@ set ( lxqt-admin-user_SRCS userdialog.cpp groupdialog.cpp usermanager.cpp + misc.cpp + taskmanager.cpp + processworker.cpp ) set ( lxqt-admin-user_MOCS @@ -18,6 +21,8 @@ set ( lxqt-admin-user_MOCS userdialog.h groupdialog.h usermanager.h + taskmanager.h + processworker.h ) set( lxqt-admin-user_UIS diff --git a/lxqt-admin-user/lxqt-admin-user-helper.default b/lxqt-admin-user/lxqt-admin-user-helper.default index 24e4c396..fde1ecce 100755 --- a/lxqt-admin-user/lxqt-admin-user-helper.default +++ b/lxqt-admin-user/lxqt-admin-user-helper.default @@ -6,6 +6,12 @@ useradd|usermod|userdel|groupadd|groupmod|groupdel|passwd|gpasswd) export LC_ALL=C exec "$@" ;; +lxqt-admin-user-password) + export LD_LIBRARY_PATH=/usr/local/lib64 + export PATH=$PATH:/usr/local/bin: + export QT_QPA_PLATFORMTHEME=lxqt + exec "$@" + ;; *) echo "Command '$1' is not allowed!" exit 1 diff --git a/lxqt-admin-user/mainwindow.cpp b/lxqt-admin-user/mainwindow.cpp index c0a590c5..3c9dd411 100644 --- a/lxqt-admin-user/mainwindow.cpp +++ b/lxqt-admin-user/mainwindow.cpp @@ -40,11 +40,15 @@ MainWindow::MainWindow(): ui.userList->sortByColumn(0, Qt::AscendingOrder); ui.groupList->sortByColumn(0, Qt::AscendingOrder); + TaskManager::instance()->setWidget(this); + connect(ui.actionAdd, &QAction::triggered, this, &MainWindow::onAdd); connect(ui.actionDelete, &QAction::triggered, this, &MainWindow::onDelete); connect(ui.actionProperties, &QAction::triggered, this, &MainWindow::onEditProperties); connect(ui.actionChangePasswd, &QAction::triggered, this, &MainWindow::onChangePasswd); connect(ui.actionRefresh, &QAction::triggered, this, &MainWindow::reload); + connect(ui.tabWidget, &QTabWidget::currentChanged, this, &MainWindow::onCurrentTabChanged); + connect(TaskManager::instance(), &TaskManager::stateChanged, this, &MainWindow::onProcessing); #ifdef Q_OS_FREEBSD //Disable group gpasswd for FreeBSD connect(ui.tabWidget, &QTabWidget::currentChanged, [this](int index) { @@ -115,6 +119,17 @@ void MainWindow::reloadGroups() ui.groupList->addTopLevelItems(items); } +void MainWindow::lockWidget(bool lock) +{ + ui.actionAdd->setDisabled(lock); + ui.actionDelete->setDisabled(lock); + ui.actionProperties->setDisabled(lock); + ui.actionChangePasswd->setDisabled(lock); + ui.actionRefresh->setDisabled(lock); + ui.showSystemUsers->setDisabled(lock); + ui.tabWidget->setDisabled(lock); +} + void MainWindow::reload() { reloadUsers(); reloadGroups(); @@ -148,11 +163,8 @@ void MainWindow::onAdd() UserDialog dlg(mUserManager, &newUser, this); if(dlg.exec() == QDialog::Accepted) { - mUserManager->addUser(&newUser); - QByteArray newPasswd; - if(getNewPassword(newUser.name(), newPasswd)) { - mUserManager->changePassword(&newUser, newPasswd); - } + if(mUserManager->addUser(&newUser)) + mUserManager->changePassword(&newUser); } } else if (ui.tabWidget->currentIndex() == PageGroups) @@ -194,31 +206,8 @@ void MainWindow::onDelete() } } -bool MainWindow::getNewPassword(const QString& name, QByteArray& passwd) { - QInputDialog dlg(this); - dlg.setTextEchoMode(QLineEdit::Password); - dlg.setLabelText(tr("Input the new password for %1:").arg(name)); - QLineEdit* edit = dlg.findChild(QString()); - if(edit) { - // NOTE: do we need to add a validator to limit the input? - // QRegExpValidator* validator = new QRegExpValidator(QRegExp(QStringLiteral("\\w*")), edit); - // edit->setValidator(validator); - } - if(dlg.exec() == QDialog::Accepted) { - passwd = dlg.textValue().toUtf8(); - if(passwd.isEmpty()) { - if(QMessageBox::question(this, tr("Confirm"), tr("Are you sure you want to set a empty password?"), QMessageBox::Ok|QMessageBox::Cancel) != QMessageBox::Ok) - return false; - } - return true; - } - return false; -} - void MainWindow::onChangePasswd() { - QString name; UserInfo* user = nullptr; - GroupInfo* group = nullptr; if(ui.tabWidget->currentIndex() == PageUsers) { QTreeWidgetItem* item = ui.userList->currentItem(); @@ -226,26 +215,7 @@ void MainWindow::onChangePasswd() { if (!user) return; - name = user->name(); - } - else if(ui.tabWidget->currentIndex() == PageGroups) - { - QTreeWidgetItem* item = ui.groupList->currentItem(); - group = groupFromItem(item); - if (!group) - return; - - name = group->name(); - } - - QByteArray newPasswd; - if(getNewPassword(name, newPasswd)) { - if(user) { - mUserManager->changePassword(user, newPasswd); - } - if(group) { - mUserManager->changePassword(group, newPasswd); - } + mUserManager->changePassword(user); } } @@ -284,3 +254,33 @@ void MainWindow::onEditProperties() } } +void MainWindow::onCurrentTabChanged(int pos) +{ + bool passwordDisabled = false; + + switch (pos) + { + case PageUsers: + break; + + default: + passwordDisabled = true; + break; + } + ui.actionChangePasswd->setDisabled(passwordDisabled); +} + +void MainWindow::onProcessing(TaskManager::State state) +{ + switch (state) + { + case TaskManager::Processing: + lockWidget(true); + break; + + case TaskManager::Finished: + lockWidget(false); + reload(); + break; + } +} diff --git a/lxqt-admin-user/mainwindow.h b/lxqt-admin-user/mainwindow.h index 8e9d6e06..f8c3c5a0 100644 --- a/lxqt-admin-user/mainwindow.h +++ b/lxqt-admin-user/mainwindow.h @@ -23,6 +23,7 @@ #include #include "ui_mainwindow.h" +#include "taskmanager.h" class UserInfo; class GroupInfo; @@ -46,9 +47,9 @@ class MainWindow : public QMainWindow private: UserInfo* userFromItem(QTreeWidgetItem* item); GroupInfo* groupFromItem(QTreeWidgetItem *item); - bool getNewPassword(const QString& name, QByteArray& passwd); void reloadUsers(); void reloadGroups(); + void lockWidget(bool lock); private Q_SLOTS: void onAdd(); @@ -57,6 +58,8 @@ private Q_SLOTS: void onChangePasswd(); void reload(); void onRowActivated(const QModelIndex& index); + void onCurrentTabChanged(int pos); + void onProcessing(TaskManager::State); private: Ui::MainWindow ui; diff --git a/lxqt-admin-user/misc.cpp b/lxqt-admin-user/misc.cpp new file mode 100644 index 00000000..2382f185 --- /dev/null +++ b/lxqt-admin-user/misc.cpp @@ -0,0 +1,64 @@ +#include +#include +#include +#include +#include "misc.h" +#include "taskmanager.h" + +bool pkexec(const QStringList& command, const QByteArray& stdinData) { + Q_ASSERT(!command.isEmpty()); + QProcess process; + qDebug() << command; + QStringList args; + args << QStringLiteral("--disable-internal-agent") + << QStringLiteral("lxqt-admin-user-helper") + << command; + + process.start(QStringLiteral("pkexec"), args); + if(!stdinData.isEmpty()) { + process.waitForStarted(); + process.write(stdinData); + process.waitForBytesWritten(); + process.closeWriteChannel(); + } + + while (!process.waitForFinished(-1)) + LXQt::Application::processEvents(); + + QByteArray pkexec_error = process.readAllStandardError(); + qDebug() << pkexec_error; + const bool succeeded = process.exitCode() == 0; + if (!succeeded) + { + QMessageBox * msg = new QMessageBox{QMessageBox::Critical, QObject::tr("Error") + , QObject::tr("Action (%1) failed:
%2
").arg(command[0]).arg(QString::fromUtf8(pkexec_error.constData()))}; + msg->setAttribute(Qt::WA_DeleteOnClose, true); + msg->show(); + } + return succeeded; +} + +bool pkexecGetOut(const QStringList &command, QString *out) +{ + Q_ASSERT(!command.isEmpty()); + QProcess process; + qDebug() << command; + QStringList args; + args << QStringLiteral("--disable-internal-agent") + << QStringLiteral("lxqt-admin-user-helper") + << command; + process.start(QStringLiteral("pkexec"), args); + process.waitForFinished(-1); + if (out) + *out = QString::fromUtf8(process.readAllStandardOutput()); + QByteArray pkexec_error = process.readAllStandardError(); + const bool succeeded = process.exitCode() == 0; + if (!succeeded) + { + QMessageBox * msg = new QMessageBox{QMessageBox::Critical, QObject::tr("Error") + , QObject::tr("Action (%1) failed:
%2
").arg(command[0]).arg(QString::fromUtf8(pkexec_error.constData()))}; + msg->setAttribute(Qt::WA_DeleteOnClose, true); + msg->show(); + } + return succeeded; +} diff --git a/lxqt-admin-user/misc.h b/lxqt-admin-user/misc.h new file mode 100644 index 00000000..ff3959da --- /dev/null +++ b/lxqt-admin-user/misc.h @@ -0,0 +1,10 @@ +#ifndef MISC_H +#define MISC_H + +#include + +bool pkexec(const QStringList& command, const QByteArray& stdinData = QByteArray()); +bool pkexecGetOut(const QStringList& command, QString *out); + + +#endif // MISC_H diff --git a/lxqt-admin-user/processworker.cpp b/lxqt-admin-user/processworker.cpp new file mode 100644 index 00000000..7f0605e6 --- /dev/null +++ b/lxqt-admin-user/processworker.cpp @@ -0,0 +1,45 @@ +#include +#include "processworker.h" + + +Worker::Worker(QObject *parent) +: QObject(parent) +{ + qRegisterMetaType("Worker::WorkerState"); +} + +Worker::~Worker() +{} + +/* *************************************************************************************** */ + +ProcessWorker::ProcessWorker(const QString &cmd, const QStringList &args, QObject *parent) +: Worker(parent), + mCmd(cmd), + mArgs(args), + mProcess(new QProcess(this)) +{ + mProcess->setProcessChannelMode(QProcess::MergedChannels); + + connect(mProcess, + static_cast(&QProcess::finished), + [=] { + if (mProcess->exitCode() || (mProcess->exitStatus() != QProcess::NormalExit)) + mState = FinishedWithError; + else + mState = Finished; + qDebug() << Q_FUNC_INFO << "cmd: " << cmd << " " << args << " ret: " << mState; + Q_EMIT notify(mState, QString::fromUtf8(mProcess->readAll())); + Q_EMIT finished(); + } ); +} + +ProcessWorker::~ProcessWorker() +{} + +void ProcessWorker::onRun() +{ + mProcess->start(mCmd, mArgs); + mState = Started; + Q_EMIT notify(mState); +} diff --git a/lxqt-admin-user/processworker.h b/lxqt-admin-user/processworker.h new file mode 100644 index 00000000..94dbc7c1 --- /dev/null +++ b/lxqt-admin-user/processworker.h @@ -0,0 +1,53 @@ +#ifndef PROCESSWORKER_H +#define PROCESSWORKER_H + +#include +#include + + +class Worker : public QObject +{ + Q_OBJECT +public: + Worker(QObject *parent = 0); + ~Worker(); + + enum WorkerState + { + Undefined, + Started, + Finished, + FinishedWithError + }; +public slots: + virtual void onRun() = 0; + +signals: + void notify(Worker::WorkerState state, QString msg = QString()); + void finished(); + +protected: + WorkerState mState; +}; + + +/* ********************************************************************************* */ + +class ProcessWorker : public Worker +{ + Q_OBJECT +public: + ProcessWorker(const QString &cmd, const QStringList &args, QObject *parent = 0); + ~ProcessWorker(); + +public slots: + virtual void onRun(); + +private: + QString mCmd; + QStringList mArgs; + QProcess *mProcess; + QStringList mResult; +}; + +#endif // PROCESSWORKER_H diff --git a/lxqt-admin-user/taskmanager.cpp b/lxqt-admin-user/taskmanager.cpp new file mode 100644 index 00000000..77dca67d --- /dev/null +++ b/lxqt-admin-user/taskmanager.cpp @@ -0,0 +1,84 @@ +#include +#include +#include +#include +#include +#include "taskmanager.h" + +TaskManager::TaskManager() +{} + +TaskManager::~TaskManager() +{} + +void TaskManager::execFirst() +{ + if (mWorkers.size()) + { + Worker *worker = mWorkers.first(); + mWorkers.pop_front(); + + QThread *thread = new QThread(); + worker->moveToThread(thread); + + connect(thread, SIGNAL(started()), worker, SLOT(onRun())); + connect(worker, SIGNAL(finished()), thread, SLOT(quit())); + connect(worker, SIGNAL(finished()), worker, SLOT(deleteLater())); + connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); + + connect(worker, SIGNAL(notify(Worker::WorkerState, QString)), this, SLOT(onWorkerStateChanged(Worker::WorkerState, QString))); + thread->start(); + } + else + Q_EMIT stateChanged(TaskManager::Finished); +} + +void TaskManager::onWorkerStateChanged(Worker::WorkerState state, QString msg) +{ + switch (state) + { + case ProcessWorker::Finished: + { + execFirst(); + } + break; + case ProcessWorker::FinishedWithError: + { + mWorkers.clear(); + QMessageBox::critical(mWidget, + QObject::tr("Error"), + msg); + Q_EMIT stateChanged(TaskManager::Finished); + } + break; + default: + break; + } +} + +void TaskManager::run() +{ + if (mWorkers.size() == 0) + return; + Q_EMIT stateChanged(TaskManager::Processing); + execFirst(); +} + +void TaskManager::setWidget(const QWidget *widget) +{ + mWidget = (QWidget *) widget; +} + +TaskManager *TaskManager::instance() +{ + if (mInstance == nullptr) + mInstance = new TaskManager; + return mInstance; +} + +void TaskManager::append(const Worker *worker) +{ + mWorkers.append((Worker *) worker); +} + +TaskManager* TaskManager::mInstance = nullptr; diff --git a/lxqt-admin-user/taskmanager.h b/lxqt-admin-user/taskmanager.h new file mode 100644 index 00000000..d612b0f5 --- /dev/null +++ b/lxqt-admin-user/taskmanager.h @@ -0,0 +1,43 @@ +#ifndef TASKMANAGER_H +#define TASKMANAGER_H + +#include +#include +#include +#include "processworker.h" + +class TaskManager : public QObject +{ + Q_OBJECT +public: + static TaskManager *instance(); + + void append(const Worker *worker); + void run(); + void setWidget(const QWidget *widget); + + enum State + { + Processing, + Finished + }; + +signals: + void stateChanged(TaskManager::State); + +private: + TaskManager(); + ~TaskManager(); + + void execFirst(); + +private slots: + void onWorkerStateChanged(Worker::WorkerState, QString); + +private: + static TaskManager *mInstance; + QWidget *mWidget; + QList mWorkers; +}; + +#endif // TASKMANAGER_H diff --git a/lxqt-admin-user/usermanager.cpp b/lxqt-admin-user/usermanager.cpp index 54cdbe18..f504effc 100644 --- a/lxqt-admin-user/usermanager.cpp +++ b/lxqt-admin-user/usermanager.cpp @@ -37,6 +37,8 @@ #include #include #include +#include "misc.h" +#include "taskmanager.h" static const QString PASSWD_FILE = QStringLiteral("/etc/passwd"); static const QString GROUP_FILE = QStringLiteral("/etc/group"); @@ -203,35 +205,6 @@ void UserManager::onFileChanged(const QString &path) { QTimer::singleShot(500, this, &UserManager::reload); } -bool UserManager::pkexec(const QStringList& command, const QByteArray& stdinData) { - Q_ASSERT(!command.isEmpty()); - QProcess process; - qDebug() << command; - QStringList args; - args << QStringLiteral("--disable-internal-agent") - << QStringLiteral("lxqt-admin-user-helper") - << command; - process.start(QStringLiteral("pkexec"), args); - if(!stdinData.isEmpty()) { - process.waitForStarted(); - process.write(stdinData); - process.waitForBytesWritten(); - process.closeWriteChannel(); - } - process.waitForFinished(-1); - QByteArray pkexec_error = process.readAllStandardError(); - qDebug() << pkexec_error; - const bool succeeded = process.exitCode() == 0; - if (!succeeded) - { - QMessageBox * msg = new QMessageBox{QMessageBox::Critical, tr("lxqt-admin-user") - , tr("Action (%1) failed:
%2
").arg(command[0]).arg(QString::fromUtf8(pkexec_error))}; - msg->setAttribute(Qt::WA_DeleteOnClose, true); - msg->show(); - } - return succeeded; -} - bool UserManager::addUser(UserInfo* user) { if(!user || user->name().isEmpty()) return false; @@ -319,7 +292,7 @@ bool UserManager::deleteUser(UserInfo* user) { return pkexec(command); } -bool UserManager::changePassword(UserInfo* user, QByteArray newPasswd) { +void UserManager::changePassword(UserInfo* user) { // In theory, the current user should be able to use "passwd" to // reset his/her own password without root permission, but... // /usr/bin/passwd is a setuid program running as root and QProcess @@ -329,16 +302,12 @@ bool UserManager::changePassword(UserInfo* user, QByteArray newPasswd) { // Maybe we can use our pkexec helper script to achieve this. } QStringList command; - command << QStringLiteral("passwd"); - command << user->name(); + command << QSL("--disable-internal-agent") << QSL("lxqt-admin-user-helper"); + command << QSL("lxqt-admin-user-password"); + command << QSL("-u") << user->name(); - // we need to type the new password for two times. - QByteArray stdinData; - stdinData += newPasswd; - stdinData += "\n"; - stdinData += newPasswd; - stdinData += "\n"; - return pkexec(command, stdinData); + TaskManager::instance()->append(new ProcessWorker(QSL("pkexec"), command)); + TaskManager::instance()->run(); } bool UserManager::addGroup(GroupInfo* group) { @@ -415,19 +384,6 @@ bool UserManager::deleteGroup(GroupInfo* group) { return pkexec(command); } -bool UserManager::changePassword(GroupInfo* group, QByteArray newPasswd) { - QStringList command; - command << QStringLiteral("gpasswd"); - command << group->name(); - - // we need to type the new password for two times. - QByteArray stdinData = newPasswd; - stdinData += "\n"; - stdinData += newPasswd; - stdinData += "\n"; - return pkexec(command, stdinData); -} - const QStringList& UserManager::availableShells() { if(mAvailableShells.isEmpty()) { QFile file(QSL("/etc/shells")); diff --git a/lxqt-admin-user/usermanager.h b/lxqt-admin-user/usermanager.h index 64a0b13e..eaa38e88 100644 --- a/lxqt-admin-user/usermanager.h +++ b/lxqt-admin-user/usermanager.h @@ -200,12 +200,11 @@ class UserManager : public QObject bool addUser(UserInfo* user); bool modifyUser(UserInfo* user, UserInfo* newSettings); bool deleteUser(UserInfo* user); - bool changePassword(UserInfo* user, QByteArray newPasswd); + void changePassword(UserInfo* user); bool addGroup(GroupInfo* group); bool modifyGroup(GroupInfo* group, GroupInfo* newSettings); bool deleteGroup(GroupInfo* group); - bool changePassword(GroupInfo* group, QByteArray newPasswd); // FIXME: add APIs to change group membership with "gpasswd" @@ -220,7 +219,6 @@ class UserManager : public QObject private: void loadUsersAndGroups(); void loadLoginDefs(); - bool pkexec(const QStringList &command, const QByteArray &stdinData = QByteArray()); Q_SIGNALS: void changed();