From 8c137ecc52c057a41c037dba5b41f5c2f8bdabf0 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Tue, 10 Jan 2023 16:21:45 +0300 Subject: [PATCH] added page to display WireGuard client information --- client/core/servercontroller.cpp | 54 ++++++++++--- client/ui/models/clientManagementModel.cpp | 49 ++++++++---- client/ui/models/clientManagementModel.h | 17 ++-- client/ui/pages_logic/ClientInfoLogic.cpp | 40 ++++++++-- client/ui/pages_logic/ClientInfoLogic.h | 8 +- .../ui/pages_logic/ClientManagementLogic.cpp | 9 +-- .../ClientInfo/PageClientInfoOpenVPN.qml | 7 +- .../ClientInfo/PageClientInfoWireGuard.qml | 79 +++++++++++++++++++ client/ui/qml/Pages/PageClientManagement.qml | 29 +++---- 9 files changed, 216 insertions(+), 76 deletions(-) diff --git a/client/core/servercontroller.cpp b/client/core/servercontroller.cpp index ca00f91d..f165dd74 100644 --- a/client/core/servercontroller.cpp +++ b/client/core/servercontroller.cpp @@ -810,23 +810,26 @@ ErrorCode ServerController::getClientsList(const ServerCredentials &credentials, genVarsForScript(credentials, container)), cbReadStdOut); // TODO error processing if (!stdOut.isEmpty()) { - QStringList clietnsNames = stdOut.split("\n", Qt::SkipEmptyParts); - clietnsNames.removeAll("AmneziaReq.crt"); + QStringList certsIds = stdOut.split("\n", Qt::SkipEmptyParts); + certsIds.removeAll("AmneziaReq.crt"); QByteArray clientsTableString = getTextFileFromContainer(container, credentials, "opt/amnezia/openvpn/clientsTable"); QJsonObject clientsTable = QJsonDocument::fromJson(clientsTableString).object(); - for (auto &clietnId : clietnsNames) { - clietnId.replace(".crt", ""); - if (!clientsTable.contains(clietnId)) { + int count = 0; + for (auto &openvpnCertId : certsIds) { + openvpnCertId.replace(".crt", ""); + if (!clientsTable.contains(openvpnCertId)) { stdOut.clear(); error = runScript(credentials, - replaceVars(QString("sudo docker exec -i $CONTAINER_NAME bash -c 'cat /opt/amnezia/openvpn/pki/issued/%1.crt'").arg(clietnId), + replaceVars(QString("sudo docker exec -i $CONTAINER_NAME bash -c 'cat /opt/amnezia/openvpn/pki/issued/%1.crt'").arg(openvpnCertId), genVarsForScript(credentials, container)), cbReadStdOut); // TODO error processing QJsonObject client; - client["name"] = ""; - client["certificate"] = stdOut; - clientsTable[clietnId] = client; + client["openvpnCertId"] = openvpnCertId; + client["clientName"] = QString("Client %1").arg(count); + client["openvpnCertData"] = stdOut; + clientsTable[openvpnCertId] = client; + count++; } } QByteArray newClientsTableString = QJsonDocument(clientsTable).toJson(); @@ -837,7 +840,33 @@ ErrorCode ServerController::getClientsList(const ServerCredentials &credentials, clietns = clientsTable; } } else if (mainProtocol == Proto::WireGuard) { + QString wireguardConfigString = getTextFileFromContainer(container, credentials, "opt/amnezia/wireguard/wg0.conf"); + auto configSections = wireguardConfigString.split("[", Qt::SkipEmptyParts); + QJsonObject clientsTable; + int count = 0; + for (const auto §ion : configSections) { + auto configLines = section.split("\n", Qt::SkipEmptyParts); + if (!configLines.contains("Peer]")) { + continue; + } + QJsonObject client; + for (const auto &line : configLines) { + auto configPair = line.split(" = ", Qt::SkipEmptyParts); + if (configPair.front() == "# Name") { + client["clientName"] = configPair.size() == 2 ? configPair.back() : ""; + } else if (configPair.front() == "PublicKey") { + client["wireguardPublicKey"] = configPair.back(); + } + } + if (client["clientName"].isNull()) { + client["clientName"] = QString("Client %1").arg(count); + count++; + } + clientsTable[client["wireguardPublicKey"].toString()] = client; + } + // TODO error processing + clietns = clientsTable; } return error; @@ -845,7 +874,12 @@ ErrorCode ServerController::getClientsList(const ServerCredentials &credentials, ErrorCode ServerController::setClientsList(const ServerCredentials &credentials, DockerContainer container, Proto mainProtocol, QJsonObject &clietns) { - auto error = uploadTextFileToContainer(container, credentials, QJsonDocument(clietns).toJson(), "opt/amnezia/openvpn/clientsTable"); + ErrorCode error = ErrorCode::NoError; + if (mainProtocol == Proto::OpenVpn) { + error = uploadTextFileToContainer(container, credentials, QJsonDocument(clietns).toJson(), "opt/amnezia/openvpn/clientsTable"); + } else if (mainProtocol == Proto::WireGuard) { + + } return error; } diff --git a/client/ui/models/clientManagementModel.cpp b/client/ui/models/clientManagementModel.cpp index c3724387..7791e3db 100644 --- a/client/ui/models/clientManagementModel.cpp +++ b/client/ui/models/clientManagementModel.cpp @@ -14,13 +14,22 @@ void ClientManagementModel::clearData() endResetModel(); } -void ClientManagementModel::setContent(const QVector &data) +void ClientManagementModel::setContent(const QVector &data) { beginResetModel(); m_content = data; endResetModel(); } +QJsonObject ClientManagementModel::getContent() +{ + QJsonObject clientsTable; + for (const auto &item : m_content) { + clientsTable[item.toJsonObject()["openvpnCertId"].toString()] = item.toJsonObject(); + } + return clientsTable; +} + int ClientManagementModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); @@ -35,14 +44,15 @@ QVariant ClientManagementModel::data(const QModelIndex &index, int role) const } if (role == NameRole) { - return m_content[index.row()].name; - } - if (role == CertIdRole) { - return m_content[index.row()].certId; - } - if (role == CertDataRole) { - return m_content[index.row()].certData; + return m_content[index.row()].toJsonObject()["clientName"].toString(); + } else if (role == OpenVpnCertIdRole) { + return m_content[index.row()].toJsonObject()["openvpnCertId"].toString(); + } else if (role == OpenVpnCertDataRole) { + return m_content[index.row()].toJsonObject()["openvpnCertData"].toString(); + } else if (role == WireGuardPublicKey) { + return m_content[index.row()].toJsonObject()["wireguardPublicKey"].toString(); } + return QVariant(); } @@ -53,15 +63,19 @@ void ClientManagementModel::setData(const QModelIndex &index, QVariant data, int return; } + auto client = m_content[index.row()].toJsonObject(); if (role == NameRole) { - m_content[index.row()].name = data.toString(); - } - if (role == CertIdRole) { - m_content[index.row()].certId = data.toString(); - } - if (role == CertDataRole) { - m_content[index.row()].certData = data.toString(); + client["clientName"] = data.toString(); + } else if (role == OpenVpnCertIdRole) { + client["openvpnCertId"] = data.toString(); + } else if (role == OpenVpnCertDataRole) { + client["openvpnCertData"] = data.toString(); + } else if (role == WireGuardPublicKey) { + client["wireguardPublicKey"] = data.toString(); + } else { + return; } + m_content[index.row()] = client; emit dataChanged(index, index); } @@ -69,7 +83,8 @@ QHash ClientManagementModel::roleNames() const { QHash roles; roles[NameRole] = "clientName"; - roles[CertIdRole] = "certId"; - roles[CertDataRole] = "certData"; + roles[OpenVpnCertIdRole] = "openvpnCertId"; + roles[OpenVpnCertDataRole] = "openvpnCertData"; + roles[WireGuardPublicKey] = "wireguardPublicKey"; return roles; } diff --git a/client/ui/models/clientManagementModel.h b/client/ui/models/clientManagementModel.h index 6f9cfbc7..81f1197e 100644 --- a/client/ui/models/clientManagementModel.h +++ b/client/ui/models/clientManagementModel.h @@ -12,21 +12,16 @@ class ClientManagementModel : public QAbstractListModel public: enum ClientRoles { NameRole = Qt::UserRole + 1, - CertIdRole, - CertDataRole - }; - - struct ClientInfo - { - QString name; - QString certId; - QString certData; + OpenVpnCertIdRole, + OpenVpnCertDataRole, + WireGuardPublicKey, }; ClientManagementModel(std::shared_ptr settings, QObject *parent = nullptr); void clearData(); - void setContent(const QVector &data); + void setContent(const QVector &data); + QJsonObject getContent(); int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; void setData(const QModelIndex &index, QVariant data, int role = Qt::DisplayRole); @@ -36,7 +31,7 @@ protected: private: std::shared_ptr m_settings; //TODO remove this? - QVector m_content; + QVector m_content; }; #endif // CLIENTMANAGEMENTMODEL_H diff --git a/client/ui/pages_logic/ClientInfoLogic.cpp b/client/ui/pages_logic/ClientInfoLogic.cpp index 4a401022..6065288e 100644 --- a/client/ui/pages_logic/ClientInfoLogic.cpp +++ b/client/ui/pages_logic/ClientInfoLogic.cpp @@ -16,16 +16,26 @@ void ClientInfoLogic::setCurrentClientId(int index) } void ClientInfoLogic::onUpdatePage() -{ +{ DockerContainer selectedContainer = m_settings->defaultContainer(uiLogic()->selectedServerIndex); QString selectedContainerName = ContainerProps::containerHumanNames().value(selectedContainer); set_labelCurrentVpnProtocolText(tr("Service: ") + selectedContainerName); - auto model = qobject_cast(uiLogic()->clientManagementModel()); - auto modelIndex = model->index(m_currentClientIndex); - set_lineEditNameAliasText(model->data(modelIndex, ClientManagementModel::ClientRoles::NameRole).toString()); - set_labelCertId(model->data(modelIndex, ClientManagementModel::ClientRoles::CertIdRole).toString()); - set_textAreaCertificate(model->data(modelIndex, ClientManagementModel::ClientRoles::CertDataRole).toString()); + auto protocols = ContainerProps::protocolsForContainer(selectedContainer); + if (!protocols.empty()) { + auto currentMainProtocol = protocols.front(); + + auto model = qobject_cast(uiLogic()->clientManagementModel()); + auto modelIndex = model->index(m_currentClientIndex); + + set_lineEditNameAliasText(model->data(modelIndex, ClientManagementModel::ClientRoles::NameRole).toString()); + if (currentMainProtocol == Proto::OpenVpn) { + set_labelOpenVpnCertId(model->data(modelIndex, ClientManagementModel::ClientRoles::OpenVpnCertIdRole).toString()); + set_textAreaOpenVpnCertData(model->data(modelIndex, ClientManagementModel::ClientRoles::OpenVpnCertDataRole).toString()); + } else if (currentMainProtocol == Proto::WireGuard) { + set_textAreaWireGuardKeyData(model->data(modelIndex, ClientManagementModel::ClientRoles::WireGuardPublicKey).toString()); + } + } } void ClientInfoLogic::onLineEditNameAliasEditingFinished() @@ -34,10 +44,24 @@ void ClientInfoLogic::onLineEditNameAliasEditingFinished() auto modelIndex = model->index(m_currentClientIndex); model->setData(modelIndex, m_lineEditNameAliasText, ClientManagementModel::ClientRoles::NameRole); - m_serverController->setClientsList(); + auto clientsTable = model->getContent(); + DockerContainer selectedContainer = m_settings->defaultContainer(uiLogic()->selectedServerIndex); + auto protocols = ContainerProps::protocolsForContainer(selectedContainer); + if (!protocols.empty()) { + auto currentMainProtocol = protocols.front(); + m_serverController->setClientsList(m_settings->serverCredentials(uiLogic()->selectedServerIndex), + selectedContainer, + currentMainProtocol, + clientsTable); + } } -void ClientInfoLogic::onRevokeCertificateClicked() +void ClientInfoLogic::onRevokeOpenVpnCertificateClicked() +{ + +} + +void ClientInfoLogic::onRevokeWireGuardKeyClicked() { } diff --git a/client/ui/pages_logic/ClientInfoLogic.h b/client/ui/pages_logic/ClientInfoLogic.h index 4d2f7e97..25d57fb1 100644 --- a/client/ui/pages_logic/ClientInfoLogic.h +++ b/client/ui/pages_logic/ClientInfoLogic.h @@ -10,9 +10,10 @@ class ClientInfoLogic : public PageLogicBase Q_OBJECT AUTO_PROPERTY(QString, lineEditNameAliasText) - AUTO_PROPERTY(QString, labelCertId) - AUTO_PROPERTY(QString, textAreaCertificate) + AUTO_PROPERTY(QString, labelOpenVpnCertId) + AUTO_PROPERTY(QString, textAreaOpenVpnCertData) AUTO_PROPERTY(QString, labelCurrentVpnProtocolText) + AUTO_PROPERTY(QString, textAreaWireGuardKeyData) public: ClientInfoLogic(UiLogic *uiLogic, QObject *parent = nullptr); @@ -23,7 +24,8 @@ public: public slots: void onUpdatePage() override; void onLineEditNameAliasEditingFinished(); - void onRevokeCertificateClicked(); + void onRevokeOpenVpnCertificateClicked(); + void onRevokeWireGuardKeyClicked(); private: int m_currentClientIndex; diff --git a/client/ui/pages_logic/ClientManagementLogic.cpp b/client/ui/pages_logic/ClientManagementLogic.cpp index 96073823..7e88d715 100644 --- a/client/ui/pages_logic/ClientManagementLogic.cpp +++ b/client/ui/pages_logic/ClientManagementLogic.cpp @@ -13,6 +13,7 @@ ClientManagementLogic::ClientManagementLogic(UiLogic *logic, QObject *parent): void ClientManagementLogic::onUpdatePage() { + qobject_cast(uiLogic()->clientManagementModel())->clearData(); DockerContainer selectedContainer = m_settings->defaultContainer(uiLogic()->selectedServerIndex); QString selectedContainerName = ContainerProps::containerHumanNames().value(selectedContainer); set_labelCurrentVpnProtocolText(tr("Service: ") + selectedContainerName); @@ -26,13 +27,9 @@ void ClientManagementLogic::onUpdatePage() ErrorCode e = m_serverController->getClientsList(m_settings->serverCredentials(uiLogic()->selectedServerIndex), selectedContainer, m_currentMainProtocol, clients); } - QVector clientsArray; + QVector clientsArray; for (auto &clientId : clients.keys()) { - ClientManagementModel::ClientInfo clientInfo; - clientInfo.certId = clientId; - clientInfo.name = clients[clientId].toObject()["name"].toString(); - clientInfo.certData = clients[clientId].toObject()["certificate"].toString(); - clientsArray.push_back(clientInfo); + clientsArray.push_back(clients[clientId].toObject()); } qobject_cast(uiLogic()->clientManagementModel())->setContent(clientsArray); } diff --git a/client/ui/qml/Pages/ClientInfo/PageClientInfoOpenVPN.qml b/client/ui/qml/Pages/ClientInfo/PageClientInfoOpenVPN.qml index aab4cc49..ee8c7241 100644 --- a/client/ui/qml/Pages/ClientInfo/PageClientInfoOpenVPN.qml +++ b/client/ui/qml/Pages/ClientInfo/PageClientInfoOpenVPN.qml @@ -9,7 +9,6 @@ import "../../Config" PageClientInfoBase { id: root protocol: ProtocolEnum.OpenVpn - BackButton { id: back } @@ -71,7 +70,7 @@ PageClientInfoBase { LabelType { Layout.fillWidth: true horizontalAlignment: Text.AlignHCenter - text: ClientInfoLogic.labelCertId + text: ClientInfoLogic.labelOpenVpnCertId } LabelType { @@ -87,7 +86,7 @@ PageClientInfoBase { textArea.readOnly: true textArea.wrapMode: TextEdit.WrapAnywhere textArea.verticalAlignment: Text.AlignTop - textArea.text: ClientInfoLogic.textAreaCertificate + textArea.text: ClientInfoLogic.textAreaOpenVpnCertData } BlueButtonType { @@ -95,7 +94,7 @@ PageClientInfoBase { Layout.preferredHeight: 41 text: qsTr("Revoke Certificate") onClicked: { - ClientInfoLogic.onRevokeCertificateClicked() + ClientInfoLogic.onRevokeOpenVpnCertificateClicked() } } } diff --git a/client/ui/qml/Pages/ClientInfo/PageClientInfoWireGuard.qml b/client/ui/qml/Pages/ClientInfo/PageClientInfoWireGuard.qml index 088ca3ec..78b49e31 100644 --- a/client/ui/qml/Pages/ClientInfo/PageClientInfoWireGuard.qml +++ b/client/ui/qml/Pages/ClientInfo/PageClientInfoWireGuard.qml @@ -7,5 +7,84 @@ import "../../Controls" import "../../Config" PageClientInfoBase { + id: root + protocol: ProtocolEnum.WireGuard + BackButton { + id: back + } + Caption { + id: caption + text: qsTr("Client Info") + } + + Flickable { + id: fl + width: root.width + anchors.top: caption.bottom + anchors.topMargin: 20 + anchors.bottom: root.bottom + anchors.bottomMargin: 20 + anchors.left: root.left + anchors.leftMargin: 30 + anchors.right: root.right + anchors.rightMargin: 30 + + contentHeight: content.height + clip: true + + ColumnLayout { + id: content + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + LabelType { + Layout.fillWidth: true + font.pixelSize: 20 + horizontalAlignment: Text.AlignHCenter + text: ClientInfoLogic.labelCurrentVpnProtocolText + } + + LabelType { + height: 21 + text: qsTr("Client name") + } + + TextFieldType { + Layout.fillWidth: true + Layout.preferredHeight: 31 + text: ClientInfoLogic.lineEditNameAliasText + onEditingFinished: { + ClientInfoLogic.lineEditNameAliasText = text + ClientInfoLogic.onLineEditNameAliasEditingFinished() + } + } + + LabelType { + Layout.topMargin: 20 + height: 21 + text: qsTr("Public Key") + } + + TextAreaType { + Layout.preferredHeight: 200 + Layout.fillWidth: true + + textArea.readOnly: true + textArea.wrapMode: TextEdit.WrapAnywhere + textArea.verticalAlignment: Text.AlignTop + textArea.text: ClientInfoLogic.textAreaWireGuardKeyData + } + + BlueButtonType { + Layout.fillWidth: true + Layout.preferredHeight: 41 + text: qsTr("Revoke Key") + onClicked: { + ClientInfoLogic.onRevokeWireGuardKeyClicked() + } + } + } + } } diff --git a/client/ui/qml/Pages/PageClientManagement.qml b/client/ui/qml/Pages/PageClientManagement.qml index cb75bb81..257b04cc 100644 --- a/client/ui/qml/Pages/PageClientManagement.qml +++ b/client/ui/qml/Pages/PageClientManagement.qml @@ -2,6 +2,7 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts import QtQuick.Shapes 1.4 +import SortFilterProxyModel 0.2 import PageEnum 1.0 import "./" import "../Controls" @@ -45,6 +46,12 @@ PageBase { text: ServerSettingsLogic.labelCurrentVpnProtocolText } + SortFilterProxyModel { + id: proxyClientManagementModel + sourceModel: UiLogic.clientManagementModel + sorters: RoleSorter { roleName: "clientName" } + } + ListView { id: lv_clients width: parent.width @@ -56,7 +63,7 @@ PageBase { topMargin: 10 spacing: 10 clip: true - model: UiLogic.clientManagementModel + model: proxyClientManagementModel highlightRangeMode: ListView.ApplyRange highlightMoveVelocity: -1 delegate: Item { @@ -69,7 +76,7 @@ PageBase { hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: { - ClientManagementLogic.onClientItemClicked(index) + ClientManagementLogic.onClientItemClicked(proxyClientManagementModel.mapToSource(index)) } } @@ -96,23 +103,11 @@ PageBase { } } - Text { - x: 10 - y: 10 - font.family: "Lato" - font.styleName: "normal" - color: "#181922" - verticalAlignment: Text.AlignVCenter - wrapMode: Text.Wrap - text: clientName - } - LabelType { x: 20 - y: 40 -// width: 141 - height: 16 - text: certId + y: 20 + font.pixelSize: 20 + text: clientName } } }