diff --git a/kcms/CMakeLists.txt b/kcms/CMakeLists.txt --- a/kcms/CMakeLists.txt +++ b/kcms/CMakeLists.txt @@ -33,6 +33,7 @@ add_subdirectory( ksmserver ) add_subdirectory( lookandfeel ) add_subdirectory( nightcolor ) +add_subdirectory( users ) add_subdirectory( hardware ) add_subdirectory( desktoppaths ) diff --git a/kcms/users/CMakeLists.txt b/kcms/users/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/kcms/users/CMakeLists.txt @@ -0,0 +1,6 @@ +add_definitions(-DTRANSLATION_DOMAIN=\"user_manager\") + +add_subdirectory(src) + +install( FILES user_manager.desktop DESTINATION ${SERVICES_INSTALL_DIR}) +kpackage_install_package(package user_manager kcms) diff --git a/kcms/users/package/contents/ui/ChangePassword.qml b/kcms/users/package/contents/ui/ChangePassword.qml new file mode 100644 --- /dev/null +++ b/kcms/users/package/contents/ui/ChangePassword.qml @@ -0,0 +1,71 @@ +/* + Copyright 2020 Carson Black + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see . +*/ + +import QtQuick 2.6 +import QtQuick.Dialogs 1.1 +import QtQuick.Layouts 1.3 +import QtQuick.Controls 2.5 + +import org.kde.kirigami 2.8 as Kirigami +import org.kde.users 1.0 + +Kirigami.OverlaySheet { + function openAndClear() { + verifyField.text = "" + passwordField.text = "" + this.open() + } + + property var account + + ColumnLayout { + id: mainColumn + spacing: Kirigami.Units.largeSpacing*5 + + Kirigami.Heading { + text: i18n("Change Password") + } + Kirigami.FormLayout { + Kirigami.PasswordField { + id: passwordField + Kirigami.FormData.label: i18n("Password:") + } + Kirigami.PasswordField { + id: verifyField + Kirigami.FormData.label: i18n("Confirm password:") + } + Kirigami.InlineMessage { + id: passwordWarning + + type: Kirigami.MessageType.Error + text: i18n("Passwords must match") + visible: passwordField.text != "" && verifyField.text != "" && passwordField.text != verifyField.text + } + Button { + text: i18n("Set Password") + enabled: !passwordWarning.visible && verifyField.text && passwordField.text + onClicked: { + user.password = passwordField.text + ChangePassword.window.hide() + } + } + } + } +} \ No newline at end of file diff --git a/kcms/users/package/contents/ui/CreateUser.qml b/kcms/users/package/contents/ui/CreateUser.qml new file mode 100644 --- /dev/null +++ b/kcms/users/package/contents/ui/CreateUser.qml @@ -0,0 +1,77 @@ +/* + Copyright 2020 Carson Black + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see . +*/ + +import QtQuick 2.6 +import QtQuick.Dialogs 1.1 +import QtQuick.Layouts 1.3 +import QtQuick.Controls 2.5 + +import org.kde.kcm 1.2 as KCM +import org.kde.kirigami 2.4 as Kirigami +import org.kde.users 1.0 + +KCM.SimpleKCM { + title: i18n("Create User") + + width: mainColumn.childrenRect.width + (Kirigami.Units.largeSpacing*10) + height: mainColumn.childrenRect.height + (Kirigami.Units.largeSpacing*10) + + onVisibleChanged: userNameField.text = realNameField.text = verifyField.text = passwordField.text = "" + + Kirigami.FormLayout { + anchors.centerIn: parent + TextField { + id: realNameField + Kirigami.FormData.label: i18n("Name:") + } + TextField { + id: userNameField + Kirigami.FormData.label: i18n("Username:") + validator: RegExpValidator { + regExp: /^[a-z_]([a-z0-9_-]{0,31}|[a-z0-9_-]{0,30}\$)$/ + } + } + TextField { + id: passwordField + echoMode: TextInput.Password + Kirigami.FormData.label: i18n("Password:") + } + TextField { + id: verifyField + echoMode: TextInput.Password + Kirigami.FormData.label: i18n("Confirm password:") + } + Kirigami.InlineMessage { + id: passwordWarning + + type: Kirigami.MessageType.Error + text: i18n("Passwords must match") + visible: passwordField.text != "" && verifyField.text != "" && passwordField.text != verifyField.text + } + Button { + text: i18n("Create") + enabled: !passwordWarning.visible && verifyField.text && passwordField.text && realNameField.text && userNameField.text + onClicked: { + UserController.createUser(userNameField.text, realNameField.text, passwordField.text) + CreateUser.window.hide() + } + } + } +} \ No newline at end of file diff --git a/kcms/users/package/contents/ui/UserDetailsPage.qml b/kcms/users/package/contents/ui/UserDetailsPage.qml new file mode 100644 --- /dev/null +++ b/kcms/users/package/contents/ui/UserDetailsPage.qml @@ -0,0 +1,183 @@ +/* + Copyright 2019 Nicolas Fella + Copyright 2020 Carson Black + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see . +*/ + +import QtQuick 2.6 +import QtQuick.Layouts 1.3 +import QtQuick.Controls 2.5 +import QtQuick.Dialogs 1.3 + +import org.kde.kcm 1.2 +import org.kde.kirigami 2.8 as Kirigami +import org.kde.users 1.0 + +SimpleKCM { + id: connectionEditorPage + + title: user.realName + + property variant user + + Connections { + target: kcm + + onApply: { + user.realName = realNametextField.text + user.email = emailTextField.text + user.name = userNameField.text + user.administrator = (usertypeBox.currentIndex == 1) + user.apply() + } + } + + Menu { + id: imagePickerMenu + + MenuItem { + text: i18n("Change account picture") + icon.name: "folder-open" + onClicked: fileDialog.open() + } + + MenuItem { + text: i18n("Delete account picture") + icon.name: "edit-delete" + onClicked: { + kcm.needsSave = true + user.face = "" + } + } + } + + FileDialog { + id: fileDialog + title: "Choose a picture" + folder: shortcuts.pictures + onAccepted: { + kcm.needsSave = true + user.face = fileDialog.fileUrl + } + } + + ColumnLayout { + + RowLayout { + Layout.alignment: Qt.AlignHCenter + ToolButton { + property int size: 6 * Kirigami.Units.gridUnit + + icon.name: user.faceValid ? "" : "user-identity" + icon.width: size + icon.height: size + + Image { + source: user.face + visible: user.faceValid + sourceSize: Qt.size(parent.size, parent.size) + anchors { + fill: parent + margins: Kirigami.Units.smallSpacing + } + } + + Layout.alignment: Qt.AlignHCenter + Layout.maximumHeight: size + Kirigami.smallSpacing + Layout.minimumHeight: size + Kirigami.smallSpacing + Layout.maximumWidth: size + Kirigami.smallSpacing + Layout.minimumWidth: size + Kirigami.smallSpacing + + onClicked: imagePickerMenu.popup() + } + TextField { + id: realNametextField + focus: true + text: user.realName + placeholderText: i18nc("Example name", "John Doe") + + onEditingFinished: { + kcm.needsSave = true + } + } + } + Kirigami.FormLayout { + TextField { + id: userNameField + focus: true + text: user.name + Kirigami.FormData.label: i18n("Username:") + + onTextChanged: { + kcm.needsSave = true + } + } + + ComboBox { + id: usertypeBox + + model: [ + i18n("Standard"), + i18n("Administrator"), + ] + + Kirigami.FormData.label: i18n("Account type:") + + currentIndex: user.administrator ? 1 : 0 + + onActivated: { + kcm.needsSave = true + } + } + + TextField { + id: emailTextField + focus: true + text: user.email + placeholderText: i18nc("Example email address", "john.doe@kde.org") + Kirigami.FormData.label: i18n("Email address:") + + onTextChanged: { + kcm.needsSave = true + } + } + + Button { + text: i18n("Change Password") + onClicked: { + changePassword.account = user + changePassword.openAndClear() + } + } + + Item { + Layout.preferredHeight: deleteUser.height + } + + Button { + id: deleteUser + + text: i18n("Delete User") + icon.name: "delete" + onClicked: UserController.deleteUser(user.uid) + } + } + } + + ChangePassword { id: changePassword; account: user } +} diff --git a/kcms/users/package/contents/ui/main.qml b/kcms/users/package/contents/ui/main.qml new file mode 100644 --- /dev/null +++ b/kcms/users/package/contents/ui/main.qml @@ -0,0 +1,123 @@ +/* + Copyright 2019 Nicolas Fella + Copyright 2020 Carson Black + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see . +*/ + +import QtQuick 2.6 +import QtQuick.Dialogs 1.1 as Dialogs +import QtQuick.Layouts 1.3 +import QtQuick.Controls 2.5 as QQC2 + +import org.kde.kcm 1.2 as KCM +import org.kde.kirigami 2.4 as Kirigami // for Kirigami.Units +import org.kde.kcoreaddons 1.0 as KCoreAddons +import org.kde.users 1.0 + +KCM.ScrollViewKCM { + id: root + + title: i18n("Manage Users") + + LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft + LayoutMirroring.childrenInherit: true + + Component.onCompleted: { + Kirigami.Settings.isMobile && userList.itemAtIndex(0).openPage() + kcm.columnWidth = Kirigami.Units.gridUnit * 20 + } + + view: ListView { + id: userList + model: UserModel + + delegate: Kirigami.AbstractListItem { + width: userList.width + contentItem: Row { + spacing: Kirigami.Units.largeSpacing + + Image { + source: model.face + height: col.height + width: height + visible: model.faceValid + } + Kirigami.Icon { + source: "user-identity" + height: col.height + width: height + visible: !model.faceValid + } + + Column { + id: col + QQC2.Label { + text: i18n("Your Account") + visible: model.loggedIn + opacity: 0.5 + } + Kirigami.Heading { + level: 3 + text: model.realName + } + QQC2.Label { + text: model.name + opacity: 0.8 + } + } + } + + highlighted: index == userList.currentIndex + + function openPage() { + userList.currentIndex = index + kcm.push("UserDetailsPage.qml", {user: User}) + } + + Component.onCompleted: () => { + if (loggedIn) { + kcm.push("UserDetailsPage.qml", {user: User}) + } + } + + onClicked: openPage() + } + } + + footer: RowLayout { + + Item { + Layout.fillWidth: true + } + + QQC2.Button { + Layout.alignment: Qt.AlignRight + + icon.name: "list-add" + + text: i18n("Add New User") + + onClicked: { + kcm.push("CreateUser.qml") + userList.currentIndex = -1 + } + } + } + + Item {} // For some reason, this being here keeps the layout from breaking. +} diff --git a/kcms/users/package/metadata.desktop b/kcms/users/package/metadata.desktop new file mode 100644 --- /dev/null +++ b/kcms/users/package/metadata.desktop @@ -0,0 +1,17 @@ +[Desktop Entry] +Name=User Accounts +Comment=Manage users +Icon=system-users +Keywords= +Type=Service +X-KDE-ParentApp= +X-KDE-PluginInfo-Author=Nicolas Fella +X-KDE-PluginInfo-Email=nicolas.fella@gmx.de +X-KDE-PluginInfo-License=GPL-2.0+ +X-KDE-PluginInfo-Name=user_manager +X-KDE-PluginInfo-Version= +X-KDE-PluginInfo-Website= +X-KDE-ServiceTypes=Plasma/Generic +X-Plasma-API=declarativeappletscript + +X-Plasma-MainScript=ui/main.qml diff --git a/kcms/users/src/CMakeLists.txt b/kcms/users/src/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/kcms/users/src/CMakeLists.txt @@ -0,0 +1,46 @@ +set(user_manager_SRCS + kcm.cpp + user.cpp + usermodel.cpp + usercontroller.cpp +) + +set_source_files_properties(org.freedesktop.Accounts.xml + PROPERTIES NO_NAMESPACE TRUE) + +set_source_files_properties(org.freedesktop.Accounts.User.xml + PROPERTIES NO_NAMESPACE TRUE) + +qt5_add_dbus_interface(user_manager_SRCS + org.freedesktop.Accounts.xml + accounts_interface +) + +qt5_add_dbus_interface(user_manager_SRCS + org.freedesktop.Accounts.User.xml + user_interface +) + +set(login1_manager_xml org.freedesktop.login1.Manager.xml) +set_source_files_properties(${login1_manager_xml} PROPERTIES INCLUDE "usersessions.h") +qt5_add_dbus_interface(user_manager_SRCS + ${login1_manager_xml} + login1_interface +) + +add_library(user_manager MODULE ${user_manager_SRCS}) + +target_link_libraries(user_manager + Qt5::Quick + Qt5::QuickWidgets + KF5::CoreAddons + KF5::Declarative + KF5::I18n + KF5::QuickAddons + Qt5::DBus + crypt +) + +kcoreaddons_desktop_to_json(user_manager "../user_manager.desktop" SERVICE_TYPES kcmodule.desktop) + +install(TARGETS user_manager DESTINATION ${PLUGIN_INSTALL_DIR}/kcms) diff --git a/kcms/users/src/kcm.h b/kcms/users/src/kcm.h new file mode 100644 --- /dev/null +++ b/kcms/users/src/kcm.h @@ -0,0 +1,41 @@ +/* + Copyright 2016-2018 Jan Grulich + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see . +*/ + +#pragma once + +#include + +class QQuickView; + +class KCMUser : public KQuickAddons::ConfigModule +{ + Q_OBJECT +public: + KCMUser(QObject *parent = nullptr, const QVariantList &args = QVariantList()); + ~KCMUser() override; + +signals: + Q_SCRIPTABLE void apply(); + +public Q_SLOTS: + void defaults() override; + void load() override; + void save() override; +}; diff --git a/kcms/users/src/kcm.cpp b/kcms/users/src/kcm.cpp new file mode 100644 --- /dev/null +++ b/kcms/users/src/kcm.cpp @@ -0,0 +1,89 @@ +/* + Copyright 2016-2018 Jan Grulich + Copyright 2019 Nicolas Fella + Copyright 2020 Carson Black + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see . +*/ + +#include "kcm.h" + +// KDE +#include +#include +#include +// Qt +#include +#include +#include + +#include "user.h" +#include "usermodel.h" +#include "usercontroller.h" + +Q_LOGGING_CATEGORY(USER_MANAGER, "user_manager") + +K_PLUGIN_FACTORY_WITH_JSON(KCMUserFactory, "user_manager.json", registerPlugin();) + + +KCMUser::KCMUser(QObject *parent, const QVariantList &args) + : KQuickAddons::ConfigModule(parent, args) +{ + KAboutData* about = new KAboutData(QStringLiteral("user_manager"), i18n("Manage user accounts"), + QStringLiteral("0.1"), QString(), KAboutLicense::GPL); + about->addAuthor(i18n("Nicolas Fella"), QString(), QStringLiteral("nicolas.fella@gmx.de")); + about->addAuthor(i18n("Carson Black"), QString(), QStringLiteral("uhhadd@gmail.com")); + setAboutData(about); + setButtons(Apply); + + qmlRegisterSingletonType("org.kde.users", 1, 0, "UserModel", [](QQmlEngine *engine, QJSEngine *scriptEngine) -> QObject* { + Q_UNUSED(engine) + Q_UNUSED(scriptEngine) + + UserModel *obj = new UserModel(); + return obj; + }); + qmlRegisterSingletonType("org.kde.users", 1, 0, "UserController", [](QQmlEngine *engine, QJSEngine *scriptEngine) -> QObject* { + Q_UNUSED(engine) + Q_UNUSED(scriptEngine) + + UserController *obj = new UserController(); + return obj; + }); +} + +KCMUser::~KCMUser() +{ +} + +void KCMUser::defaults() +{ + KQuickAddons::ConfigModule::defaults(); +} + +void KCMUser::load() +{ + KQuickAddons::ConfigModule::load(); +} + +void KCMUser::save() +{ + KQuickAddons::ConfigModule::save(); + Q_EMIT apply(); +} + +#include "kcm.moc" diff --git a/kcms/users/src/org.freedesktop.Accounts.User.xml b/kcms/users/src/org.freedesktop.Accounts.User.xml new file mode 100644 --- /dev/null +++ b/kcms/users/src/org.freedesktop.Accounts.User.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/kcms/users/src/org.freedesktop.Accounts.xml b/kcms/users/src/org.freedesktop.Accounts.xml new file mode 100644 --- /dev/null +++ b/kcms/users/src/org.freedesktop.Accounts.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/kcms/users/src/org.freedesktop.login1.Manager.xml b/kcms/users/src/org.freedesktop.login1.Manager.xml new file mode 100644 --- /dev/null +++ b/kcms/users/src/org.freedesktop.login1.Manager.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + diff --git a/kcms/users/src/user.h b/kcms/users/src/user.h new file mode 100644 --- /dev/null +++ b/kcms/users/src/user.h @@ -0,0 +1,101 @@ +/* + Copyright 2019 Nicolas Fella + Copyright 2020 Carson Black + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see . +*/ + +#pragma once + +#include +#include +#include +#include + +class OrgFreedesktopAccountsUserInterface; + +class User : public QObject { + Q_OBJECT + + Q_PROPERTY(int uid READ uid WRITE setUid NOTIFY uidChanged ) + Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged ) + + Q_PROPERTY(QString realName READ realName WRITE setRealName NOTIFY realNameChanged ) + + Q_PROPERTY(QString email READ email WRITE setEmail NOTIFY emailChanged ) + + Q_PROPERTY(QUrl face READ face WRITE setFace NOTIFY faceChanged ) + + Q_PROPERTY(bool faceValid READ faceValid NOTIFY faceValidChanged ) + + Q_PROPERTY(QString password READ _ WRITE setPassword) + + Q_PROPERTY(bool loggedIn READ loggedIn CONSTANT) + + Q_PROPERTY(bool administrator READ administrator WRITE setAdministrator NOTIFY administratorChanged ) + +public: + explicit User(QObject *parent = nullptr); + + + int uid() const; + void setUid(int value); + + QString name() const; + QString realName() const; + QString email() const; + QUrl face() const; + bool faceValid() const; + bool loggedIn() const; + bool administrator() const; + QDBusObjectPath path() const; + + void setName(const QString &value); + void setRealName(const QString &value); + void setEmail(const QString &value); + void setFace(const QUrl &value); + void setPassword(const QString &value); + void setAdministrator(bool value); + void setPath(const QDBusObjectPath &path); + + QString _() { return QString(); }; + +public Q_SLOTS: + Q_SCRIPTABLE void apply(); + +signals: + + void uidChanged(int uid); + void nameChanged(const QString &name); + void realNameChanged(const QString &realName); + void emailChanged(const QString &email); + void faceChanged(const QUrl &face); + void faceValidChanged(bool faceValid); + void administratorChanged(bool administrator); + +private: + int mUid = 0; + QString mName = QString(); + QString mRealName = QString(); + QString mEmail = QString(); + QUrl mFace = QUrl(); + bool mAdministrator = false; + bool mFaceValid = false; + bool mLoggedIn = false; + QDBusObjectPath mPath; + QPointer m_dbusIface; +}; diff --git a/kcms/users/src/user.cpp b/kcms/users/src/user.cpp new file mode 100644 --- /dev/null +++ b/kcms/users/src/user.cpp @@ -0,0 +1,187 @@ +/* + Copyright 2019 Nicolas Fella + Copyright 2020 Carson Black + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see . +*/ + +#include +#include +#include +#include +#include "user_interface.h" + +User::User(QObject* parent) : QObject(parent) {} + +int User::uid() const { + return mUid; +} + +void User::setUid(int value) { + + if (mUid == value) { + return; + } + mUid = value; + Q_EMIT uidChanged(value); +} + +QString User::name() const { + return mName; +} + +void User::setName(const QString &value) { + + if (mName == value) { + return; + } + mName = value; + nameChanged(value); +} + +QString User::realName() const { + return mRealName; +} + +void User::setRealName(const QString &value) { + + if (mRealName == value) { + return; + } + mRealName = value; + realNameChanged(value); +} + +QString User::email() const { + return mEmail; +} + +void User::setEmail(const QString &value) { + + if (mEmail == value) { + return; + } + mEmail = value; + emailChanged(value); +} + +QUrl User::face() const { + return mFace; +} + +bool User::faceValid() const { + return mFaceValid; +} + +void User::setFace(const QUrl &value) { + + if (mFace == value) { + return; + } + mFace = value; + mFaceValid = QFile::exists(value.toLocalFile()); + Q_EMIT faceValidChanged(mFaceValid); + Q_EMIT faceChanged(value); +} + +bool User::administrator() const { + return mAdministrator; +} +void User::setAdministrator(bool value) { + + if (mAdministrator == value) { + return; + } + mAdministrator = value; + administratorChanged(value); +} + +void User::setPath(const QDBusObjectPath &path) { + if (!m_dbusIface.isNull()) delete m_dbusIface; + m_dbusIface = new OrgFreedesktopAccountsUserInterface(QStringLiteral("org.freedesktop.Accounts"), path.path(), QDBusConnection::systemBus(), this); + + if (!m_dbusIface->isValid() || m_dbusIface->lastError().isValid() || m_dbusIface->systemAccount()) { + return; + } + + mPath = path; + mUid = m_dbusIface->uid(); + mName = m_dbusIface->userName(); + mFace = QUrl(m_dbusIface->iconFile()); + mFaceValid = QFileInfo::exists(mFace.toLocalFile()); + mRealName = m_dbusIface->realName(); + mEmail = m_dbusIface->email(); + mAdministrator = m_dbusIface->accountType() == 1; + mLoggedIn = mUid == getuid(); +} + +static char +saltCharacter() { + char saltCharacters[] = "ABCDEFGHIJKLMNOPQRSTUVXYZ" + "abcdefghijklmnopqrstuvxyz" + "./0123456789"; + + int index = QRandomGenerator::system()->bounded(0, (sizeof(saltCharacters)/sizeof(*saltCharacters))); + + return saltCharacters[index]; +} + +static QString +saltPassword(const QString &plain) +{ + QString salt; + + salt.append("$6$"); + + for (auto i = 0; i < 16; i++) { + salt.append(saltCharacter()); + } + + salt.append("$"); + + auto stdStrPlain = plain.toStdString(); + auto cStrPlain = stdStrPlain.c_str(); + auto stdStrSalt = salt.toStdString(); + auto cStrSalt = stdStrSalt.c_str(); + + auto salted = crypt(cStrPlain, cStrSalt); + + return QString::fromUtf8(salted); +} + +void User::setPassword(const QString &password) +{ + m_dbusIface->SetPassword(saltPassword(password), QString()); +} + +QDBusObjectPath User::path() const +{ + return mPath; +} + +void User::apply() +{ + m_dbusIface->SetEmail(mEmail).waitForFinished(); + m_dbusIface->SetRealName(mRealName).waitForFinished(); + m_dbusIface->SetIconFile(mFace.toLocalFile()).waitForFinished(); + m_dbusIface->SetAccountType(mAdministrator ? 1 : 0).waitForFinished(); +} + +bool User::loggedIn() const +{ + return mLoggedIn; +} \ No newline at end of file diff --git a/kcms/users/src/usercontroller.h b/kcms/users/src/usercontroller.h new file mode 100644 --- /dev/null +++ b/kcms/users/src/usercontroller.h @@ -0,0 +1,42 @@ +/* + Copyright 2019 Nicolas Fella + Copyright 2020 Carson Black + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see . +*/ + +#pragma once + +#include + +class OrgFreedesktopAccountsInterface; + +class UserController : public QObject +{ + + Q_OBJECT + +public: + explicit UserController(QObject *parent = nullptr); + ~UserController() override = default; + + Q_SCRIPTABLE void createUser(const QString& name, const QString& realName, const QString& password); + Q_SCRIPTABLE void deleteUser(int index); + +private: + OrgFreedesktopAccountsInterface* m_dbusInterface; +}; diff --git a/kcms/users/src/usercontroller.cpp b/kcms/users/src/usercontroller.cpp new file mode 100644 --- /dev/null +++ b/kcms/users/src/usercontroller.cpp @@ -0,0 +1,50 @@ +/* + Copyright 2019 Nicolas Fella + Copyright 2020 Carson Black + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see . +*/ + +#include "usercontroller.h" +#include "user.h" + +#include + +#include "accounts_interface.h" + +UserController::UserController(QObject *parent) : QObject(parent), + m_dbusInterface(new OrgFreedesktopAccountsInterface(QStringLiteral("org.freedesktop.Accounts"), QStringLiteral("/org/freedesktop/Accounts"), QDBusConnection::systemBus(), this)) +{ + +} + +void UserController::createUser(const QString& name, const QString& realName, const QString& password) +{ + QDBusPendingReply reply = m_dbusInterface->CreateUser(name, realName, 0); + reply.waitForFinished(); + if (reply.isValid()) { + User* createdUser = new User(this); + createdUser->setPath(reply.value()); + createdUser->setPassword(password); + delete createdUser; + } +} + +void UserController::deleteUser(int id) +{ + m_dbusInterface->DeleteUser(id, false); +} diff --git a/kcms/users/src/usermodel.h b/kcms/users/src/usermodel.h new file mode 100644 --- /dev/null +++ b/kcms/users/src/usermodel.h @@ -0,0 +1,61 @@ +/* + Copyright 2019 Nicolas Fella + Copyright 2020 Carson Black + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see . +*/ + +#pragma once + +#include + +#include "user.h" + +class OrgFreedesktopAccountsInterface; + +class UserModel + : public QAbstractListModel +{ + Q_OBJECT + +public: + enum ModelRoles { + UidRole, + NameRole, + RealNameRole, + EmailRole, + FaceRole, + FaceValidRole, + AdministratorRole, + UserRole, + LoggedInRole, + }; + + explicit UserModel(QObject* parent = nullptr); + ~UserModel() override; + + QVariant data(const QModelIndex& index, int role) const override; + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + + Q_INVOKABLE User* getLoggedInUser() const; + + QHash roleNames() const override; + +private: + OrgFreedesktopAccountsInterface* m_dbusInterface; + QVector m_userList; +}; diff --git a/kcms/users/src/usermodel.cpp b/kcms/users/src/usermodel.cpp new file mode 100644 --- /dev/null +++ b/kcms/users/src/usermodel.cpp @@ -0,0 +1,133 @@ +/* + Copyright 2019 Nicolas Fella + Copyright 2020 Carson Black + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see . +*/ + +#include "usermodel.h" + +#include +#include +#include +#include + +#include "accounts_interface.h" + +UserModel::UserModel(QObject* parent) + : QAbstractListModel(parent) + , m_dbusInterface(new OrgFreedesktopAccountsInterface(QStringLiteral("org.freedesktop.Accounts"), QStringLiteral("/org/freedesktop/Accounts"), QDBusConnection::systemBus(), this)) +{ + connect(m_dbusInterface, &OrgFreedesktopAccountsInterface::UserAdded, this, [this](const QDBusObjectPath &path) { + User *user = new User(); + user->setPath(path); + beginInsertRows(QModelIndex(), m_userList.size(), m_userList.size()); + m_userList.append(user); + endInsertRows(); + }); + + connect(m_dbusInterface, &OrgFreedesktopAccountsInterface::UserDeleted, this, [this](const QDBusObjectPath &path) { + m_userList.erase(std::remove_if(m_userList.begin(), m_userList.end(), [path](User* user) { + return user->path() == path; + }), m_userList.end()); + }); + + auto reply = m_dbusInterface->ListCachedUsers(); + reply.waitForFinished(); + + if (reply.isError()) { + qDebug() << reply.error().message(); + return; + } + + const QList users = reply.value(); + for (const QDBusObjectPath& path: users) { + User *user = new User(this); + user->setPath(path); + m_userList.append(user); + } +} + +QHash UserModel::roleNames() const +{ + QHash names = QAbstractItemModel::roleNames(); + names.insert(UidRole, "uid"); + names.insert(NameRole, "name"); + names.insert(RealNameRole, "realName"); + names.insert(EmailRole, "email"); + names.insert(FaceRole, "face"); + names.insert(AdministratorRole, "administrator"); + names.insert(UserRole, "User"); + names.insert(FaceValidRole, "faceValid"); + names.insert(LoggedInRole, "loggedIn"); + return names; +} + +UserModel::~UserModel() +{ +} + +User* UserModel::getLoggedInUser() const +{ + for (const auto user : qAsConst(m_userList)) { + if (user->loggedIn()) { + return user; + } + } + return nullptr; +} + +QVariant UserModel::data(const QModelIndex& index, int role) const +{ + if (!checkIndex(index)) + { + return QVariant(); + } + + User *user = m_userList.at(index.row()); + + switch (role) { + case NameRole: + return user->name(); + case FaceRole: + return user->face().toString(); + case RealNameRole: + return user->realName(); + case EmailRole: + return user->email(); + case AdministratorRole: + return user->administrator(); + case FaceValidRole: + return QFile::exists(user->face().toLocalFile()); + case UserRole: + return QVariant::fromValue(user); + case LoggedInRole: + return user->loggedIn(); + } + + return QVariant(); +} + +int UserModel::rowCount(const QModelIndex& parent) const +{ + if (parent.isValid()) { + //Return size 0 if we are a child because this is not a tree + return 0; + } + + return m_userList.count(); +} diff --git a/kcms/users/src/usersessions.h b/kcms/users/src/usersessions.h new file mode 100644 --- /dev/null +++ b/kcms/users/src/usersessions.h @@ -0,0 +1,55 @@ +/************************************************************************************* + * Copyright (C) 2013 by Alejandro Fiestas Olivares * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * + *************************************************************************************/ + +#pragma once + +#include +#include +#include + +struct UserInfo +{ + uint id; + QString name; + QDBusObjectPath path; +}; +Q_DECLARE_METATYPE(UserInfo) + +using UserInfoList = QList; +Q_DECLARE_METATYPE(UserInfoList) + +class QDBusPendingCallWatcher; +class OrgFreedesktopLogin1ManagerInterface; +class UserSession : public QObject +{ + Q_OBJECT + public: + explicit UserSession(QObject *parent = nullptr); + ~UserSession() override; + + public Q_SLOTS: + void UserNew(uint id); + void UserRemoved(uint id); + void listUsersSlot(QDBusPendingCallWatcher *watcher); + + Q_SIGNALS: + void userLogged(uint id, bool logged); + + private: + OrgFreedesktopLogin1ManagerInterface* m_manager; +}; diff --git a/kcms/users/user_manager.desktop b/kcms/users/user_manager.desktop new file mode 100644 --- /dev/null +++ b/kcms/users/user_manager.desktop @@ -0,0 +1,11 @@ +[Desktop Entry] +Exec=kcmshell5 user_manager +Icon=system-users +Type=Service +X-KDE-ServiceTypes=KCModule +X-KDE-Library=user_manager +X-KDE-ParentApp=kcontrol +X-KDE-Weight=40 +Name=Users +Comment= +X-KDE-Keywords=user,account