added client management

This commit is contained in:
vladimir.kuznetsov 2023-11-21 20:13:51 +07:00
parent 282f159311
commit c6a312845a
19 changed files with 675 additions and 157 deletions

View file

@ -279,7 +279,7 @@ void AmneziaApplication::initModels()
{
m_containersModel.reset(new ContainersModel(m_settings, this));
m_engine->rootContext()->setContextProperty("ContainersModel", m_containersModel.get());
connect(m_vpnConnection.get(), &VpnConnection::newVpnConfigurationCreated, m_containersModel.get(),
connect(m_configurator.get(), &VpnConfigurator::newVpnConfigCreated, m_containersModel.get(),
&ContainersModel::updateContainersConfig);
m_serversModel.reset(new ServersModel(m_settings, this));
@ -322,6 +322,11 @@ void AmneziaApplication::initModels()
m_sftpConfigModel.reset(new SftpConfigModel(this));
m_engine->rootContext()->setContextProperty("SftpConfigModel", m_sftpConfigModel.get());
m_clientManagementModel.reset(new ClientManagementModel(m_settings, this));
m_engine->rootContext()->setContextProperty("ClientManagementModel", m_clientManagementModel.get());
connect(m_configurator.get(), &VpnConfigurator::newVpnConfigCreated, m_clientManagementModel.get(),
&ClientManagementModel::appendClient);
}
void AmneziaApplication::initControllers()
@ -347,7 +352,7 @@ void AmneziaApplication::initControllers()
m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings));
m_engine->rootContext()->setContextProperty("ImportController", m_importController.get());
m_exportController.reset(new ExportController(m_serversModel, m_containersModel, m_settings, m_configurator));
m_exportController.reset(new ExportController(m_serversModel, m_containersModel, m_clientManagementModel, m_settings, m_configurator));
m_engine->rootContext()->setContextProperty("ExportController", m_exportController.get());
m_settingsController.reset(new SettingsController(m_serversModel, m_containersModel, m_languageModel, m_settings));

View file

@ -39,6 +39,7 @@
#include "ui/models/servers_model.h"
#include "ui/models/services/sftpConfigModel.h"
#include "ui/models/sites_model.h"
#include "ui/models/clientManagementModel.h"
#define amnApp (static_cast<AmneziaApplication *>(QCoreApplication::instance()))
@ -94,6 +95,7 @@ private:
QSharedPointer<LanguageModel> m_languageModel;
QSharedPointer<ProtocolsModel> m_protocolsModel;
QSharedPointer<SitesModel> m_sitesModel;
QSharedPointer<ClientManagementModel> m_clientManagementModel;
QScopedPointer<OpenVpnConfigModel> m_openVpnConfigModel;
QScopedPointer<ShadowSocksConfigModel> m_shadowSocksConfigModel;

View file

@ -10,11 +10,10 @@ AwgConfigurator::AwgConfigurator(std::shared_ptr<Settings> settings, QObject *pa
{
}
QString AwgConfigurator::genAwgConfig(const ServerCredentials &credentials,
DockerContainer container,
const QJsonObject &containerConfig, ErrorCode *errorCode)
QString AwgConfigurator::genAwgConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode)
{
QString config = WireguardConfigurator::genWireguardConfig(credentials, container, containerConfig, errorCode);
QString config = WireguardConfigurator::genWireguardConfig(credentials, container, containerConfig, clientId, errorCode);
QJsonObject jsonConfig = QJsonDocument::fromJson(config.toUtf8()).object();
QString awgConfig = jsonConfig.value(config_key::config).toString();

View file

@ -12,7 +12,7 @@ public:
AwgConfigurator(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
QString genAwgConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr);
const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode = nullptr);
};
#endif // AWGCONFIGURATOR_H

View file

@ -83,7 +83,7 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(co
}
QString OpenVpnConfigurator::genOpenVpnConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode *errorCode)
const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode)
{
ServerController serverController(m_settings);
QString config =

View file

@ -24,7 +24,7 @@ public:
};
QString genOpenVpnConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr);
const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode = nullptr);
QString processConfigWithLocalSettings(QString jsonConfig);
QString processConfigWithExportSettings(QString jsonConfig);

View file

@ -28,11 +28,11 @@ VpnConfigurator::VpnConfigurator(std::shared_ptr<Settings> settings, QObject *pa
}
QString VpnConfigurator::genVpnProtocolConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, Proto proto, ErrorCode *errorCode)
const QJsonObject &containerConfig, Proto proto, QString &clientId, ErrorCode *errorCode)
{
switch (proto) {
case Proto::OpenVpn:
return openVpnConfigurator->genOpenVpnConfig(credentials, container, containerConfig, errorCode);
return openVpnConfigurator->genOpenVpnConfig(credentials, container, containerConfig, clientId, errorCode);
case Proto::ShadowSocks:
return shadowSocksConfigurator->genShadowSocksConfig(credentials, container, containerConfig, errorCode);
@ -40,10 +40,10 @@ QString VpnConfigurator::genVpnProtocolConfig(const ServerCredentials &credentia
case Proto::Cloak: return cloakConfigurator->genCloakConfig(credentials, container, containerConfig, errorCode);
case Proto::WireGuard:
return wireguardConfigurator->genWireguardConfig(credentials, container, containerConfig, errorCode);
return wireguardConfigurator->genWireguardConfig(credentials, container, containerConfig, clientId, errorCode);
case Proto::Awg:
return awgConfigurator->genAwgConfig(credentials, container, containerConfig, errorCode);
return awgConfigurator->genAwgConfig(credentials, container, containerConfig, clientId, errorCode);
case Proto::Ikev2: return ikev2Configurator->genIkev2Config(credentials, container, containerConfig, errorCode);

View file

@ -6,7 +6,6 @@
#include "configurator_base.h"
#include "core/defs.h"
class OpenVpnConfigurator;
class ShadowSocksConfigurator;
class CloakConfigurator;
@ -16,14 +15,15 @@ class SshConfigurator;
class AwgConfigurator;
// Retrieve connection settings from server
class VpnConfigurator : ConfiguratorBase
class VpnConfigurator : public ConfiguratorBase
{
Q_OBJECT
public:
explicit VpnConfigurator(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
QString genVpnProtocolConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, Proto proto, ErrorCode *errorCode = nullptr);
const QJsonObject &containerConfig, Proto proto, QString &clientId,
ErrorCode *errorCode = nullptr);
QPair<QString, QString> getDnsForConfig(int serverIndex);
QString &processConfigWithDnsSettings(int serverIndex, DockerContainer container, Proto proto, QString &config);
@ -32,8 +32,8 @@ public:
QString &processConfigWithExportSettings(int serverIndex, DockerContainer container, Proto proto, QString &config);
// workaround for containers which is not support normal configuration
void updateContainerConfigAfterInstallation(DockerContainer container,
QJsonObject &containerConfig, const QString &stdOut);
void updateContainerConfigAfterInstallation(DockerContainer container, QJsonObject &containerConfig,
const QString &stdOut);
std::shared_ptr<OpenVpnConfigurator> openVpnConfigurator;
std::shared_ptr<ShadowSocksConfigurator> shadowSocksConfigurator;
@ -42,6 +42,10 @@ public:
std::shared_ptr<Ikev2Configurator> ikev2Configurator;
std::shared_ptr<SshConfigurator> sshConfigurator;
std::shared_ptr<AwgConfigurator> awgConfigurator;
signals:
void newVpnConfigCreated(const QString &clientId, const QString &clientName, const DockerContainer container,
ServerCredentials credentials);
};
#endif // VPN_CONFIGURATOR_H

View file

@ -177,7 +177,7 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
}
QString WireguardConfigurator::genWireguardConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode *errorCode)
const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode)
{
ServerController serverController(m_settings);
QString scriptData = amnezia::scriptData(m_configTemplate, container);
@ -205,6 +205,8 @@ QString WireguardConfigurator::genWireguardConfig(const ServerCredentials &crede
jConfig[config_key::psk_key] = connData.pskKey;
jConfig[config_key::server_pub_key] = connData.serverPubKey;
clientId = connData.clientPubKey;
return QJsonDocument(jConfig).toJson();
}

View file

@ -26,7 +26,7 @@ public:
};
QString genWireguardConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr);
const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode = nullptr);
QString processConfigWithLocalSettings(QString config);
QString processConfigWithExportSettings(QString config);

View file

@ -8,7 +8,9 @@
#include <QImage>
#include <QStandardPaths>
#include "configurators/cloak_configurator.h"
#include "configurators/openvpn_configurator.h"
#include "configurators/shadowsocks_configurator.h"
#include "configurators/wireguard_configurator.h"
#include "core/errorstrings.h"
#include "systemController.h"
@ -19,11 +21,13 @@
ExportController::ExportController(const QSharedPointer<ServersModel> &serversModel,
const QSharedPointer<ContainersModel> &containersModel,
const QSharedPointer<ClientManagementModel> &clientManagementModel,
const std::shared_ptr<Settings> &settings,
const std::shared_ptr<VpnConfigurator> &configurator, QObject *parent)
: QObject(parent),
m_serversModel(serversModel),
m_containersModel(containersModel),
m_clientManagementModel(clientManagementModel),
m_settings(settings),
m_configurator(configurator)
{
@ -75,13 +79,12 @@ void ExportController::generateFullAccessConfigAndroid()
}
#endif
void ExportController::generateConnectionConfig()
void ExportController::generateConnectionConfig(const QString &clientName)
{
clearPreviousConfig();
int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex();
ServerCredentials credentials =
qvariant_cast<ServerCredentials>(m_serversModel->data(serverIndex, ServersModel::Roles::CredentialsRole));
ServerCredentials credentials = m_serversModel->getCurrentlyProcessedServerCredentials();
DockerContainer container = static_cast<DockerContainer>(m_containersModel->getCurrentlyProcessedContainerIndex());
QModelIndex containerModelIndex = m_containersModel->index(container);
@ -93,17 +96,25 @@ void ExportController::generateConnectionConfig()
for (Proto protocol : ContainerProps::protocolsForContainer(container)) {
QJsonObject protocolConfig = m_settings->protocolConfig(serverIndex, container, protocol);
QString vpnConfig =
m_configurator->genVpnProtocolConfig(credentials, container, containerConfig, protocol, &errorCode);
QString clientId;
QString vpnConfig = m_configurator->genVpnProtocolConfig(credentials, container, containerConfig, protocol,
clientId, &errorCode);
if (errorCode) {
emit exportErrorOccurred(errorString(errorCode));
return;
}
protocolConfig.insert(config_key::last_config, vpnConfig);
containerConfig.insert(ProtocolProps::protoToString(protocol), protocolConfig);
if (protocol == Proto::OpenVpn || protocol == Proto::Awg || protocol == Proto::WireGuard) {
errorCode = m_clientManagementModel->appendClient(clientId, clientName, container, credentials);
if (errorCode) {
emit exportErrorOccurred(errorString(errorCode));
return;
}
}
}
QJsonObject config = m_settings->server(serverIndex);
QJsonObject config = m_settings->server(serverIndex); // todo change to servers_model
if (!errorCode) {
config.remove(config_key::userName);
config.remove(config_key::password);
@ -126,13 +137,12 @@ void ExportController::generateConnectionConfig()
emit exportConfigChanged();
}
void ExportController::generateOpenVpnConfig()
void ExportController::generateOpenVpnConfig(const QString &clientName)
{
clearPreviousConfig();
int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex();
ServerCredentials credentials =
qvariant_cast<ServerCredentials>(m_serversModel->data(serverIndex, ServersModel::Roles::CredentialsRole));
ServerCredentials credentials = m_serversModel->getCurrentlyProcessedServerCredentials();
DockerContainer container = static_cast<DockerContainer>(m_containersModel->getCurrentlyProcessedContainerIndex());
QModelIndex containerModelIndex = m_containersModel->index(container);
@ -141,8 +151,9 @@ void ExportController::generateOpenVpnConfig()
containerConfig.insert(config_key::container, ContainerProps::containerToString(container));
ErrorCode errorCode = ErrorCode::NoError;
QString config =
m_configurator->openVpnConfigurator->genOpenVpnConfig(credentials, container, containerConfig, &errorCode);
QString clientId;
QString config = m_configurator->openVpnConfigurator->genOpenVpnConfig(credentials, container, containerConfig,
clientId, &errorCode);
if (errorCode) {
emit exportErrorOccurred(errorString(errorCode));
return;
@ -155,16 +166,23 @@ void ExportController::generateOpenVpnConfig()
m_config.append(line + "\n");
}
m_qrCodes = generateQrCodeImageSeries(m_config.toUtf8());
errorCode = m_clientManagementModel->appendClient(clientId, clientName, container, credentials);
if (errorCode) {
emit exportErrorOccurred(errorString(errorCode));
return;
}
emit exportConfigChanged();
}
void ExportController::generateWireGuardConfig()
void ExportController::generateWireGuardConfig(const QString &clientName)
{
clearPreviousConfig();
int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex();
ServerCredentials credentials =
qvariant_cast<ServerCredentials>(m_serversModel->data(serverIndex, ServersModel::Roles::CredentialsRole));
ServerCredentials credentials = m_serversModel->getCurrentlyProcessedServerCredentials();
DockerContainer container = static_cast<DockerContainer>(m_containersModel->getCurrentlyProcessedContainerIndex());
QModelIndex containerModelIndex = m_containersModel->index(container);
@ -172,9 +190,10 @@ void ExportController::generateWireGuardConfig()
qvariant_cast<QJsonObject>(m_containersModel->data(containerModelIndex, ContainersModel::Roles::ConfigRole));
containerConfig.insert(config_key::container, ContainerProps::containerToString(container));
QString clientId;
ErrorCode errorCode = ErrorCode::NoError;
QString config = m_configurator->wireguardConfigurator->genWireguardConfig(credentials, container, containerConfig,
&errorCode);
clientId, &errorCode);
if (errorCode) {
emit exportErrorOccurred(errorString(errorCode));
return;
@ -187,6 +206,14 @@ void ExportController::generateWireGuardConfig()
m_config.append(line + "\n");
}
m_qrCodes = generateQrCodeImageSeries(m_config.toUtf8());
errorCode = m_clientManagementModel->appendClient(clientId, clientName, container, credentials);
if (errorCode) {
emit exportErrorOccurred(errorString(errorCode));
return;
}
emit exportConfigChanged();
}
@ -205,6 +232,22 @@ void ExportController::exportConfig(const QString &fileName)
SystemController::saveFile(fileName, m_config);
}
void ExportController::revokeConfig(const int row, const DockerContainer container, ServerCredentials credentials)
{
ErrorCode errorCode = m_clientManagementModel->revokeClient(row, container, credentials);
if (errorCode != ErrorCode::NoError) {
emit exportErrorOccurred(errorString(errorCode));
}
}
void ExportController::renameClient(const int row, const QString &clientName, const DockerContainer container, ServerCredentials credentials)
{
ErrorCode errorCode = m_clientManagementModel->renameClient(row, clientName, container, credentials);
if (errorCode != ErrorCode::NoError) {
emit exportErrorOccurred(errorString(errorCode));
}
}
QList<QString> ExportController::generateQrCodeImageSeries(const QByteArray &data)
{
double k = 850;

View file

@ -6,6 +6,7 @@
#include "configurators/vpn_configurator.h"
#include "ui/models/containers_model.h"
#include "ui/models/servers_model.h"
#include "ui/models/clientManagementModel.h"
#ifdef Q_OS_ANDROID
#include "platforms/android/authResultReceiver.h"
#endif
@ -16,6 +17,7 @@ class ExportController : public QObject
public:
explicit ExportController(const QSharedPointer<ServersModel> &serversModel,
const QSharedPointer<ContainersModel> &containersModel,
const QSharedPointer<ClientManagementModel> &clientManagementModel,
const std::shared_ptr<Settings> &settings,
const std::shared_ptr<VpnConfigurator> &configurator, QObject *parent = nullptr);
@ -28,15 +30,18 @@ public slots:
#if defined(Q_OS_ANDROID)
void generateFullAccessConfigAndroid();
#endif
void generateConnectionConfig();
void generateOpenVpnConfig();
void generateWireGuardConfig();
void generateConnectionConfig(const QString &clientName);
void generateOpenVpnConfig(const QString &clientName);
void generateWireGuardConfig(const QString &clientName);
QString getConfig();
QList<QString> getQrCodes();
void exportConfig(const QString &fileName);
void revokeConfig(const int row, const DockerContainer container, ServerCredentials credentials);
void renameClient(const int row, const QString &clientName, const DockerContainer container, ServerCredentials credentials);
signals:
void generateConfig(int type);
void exportErrorOccurred(const QString &errorMessage);
@ -55,6 +60,7 @@ private:
QSharedPointer<ServersModel> m_serversModel;
QSharedPointer<ContainersModel> m_containersModel;
QSharedPointer<ClientManagementModel> m_clientManagementModel;
std::shared_ptr<Settings> m_settings;
std::shared_ptr<VpnConfigurator> m_configurator;

View file

@ -1,104 +1,330 @@
#include "clientManagementModel.h"
#include <QJsonDocument>
#include <QJsonObject>
ClientManagementModel::ClientManagementModel(QObject *parent) : QAbstractListModel(parent)
{
#include "core/servercontroller.h"
}
void ClientManagementModel::clearData()
ClientManagementModel::ClientManagementModel(std::shared_ptr<Settings> settings, QObject *parent)
: m_settings(settings), QAbstractListModel(parent)
{
beginResetModel();
m_content.clear();
endResetModel();
}
void ClientManagementModel::setContent(const QVector<QVariant> &data)
{
beginResetModel();
m_content = data;
endResetModel();
}
QJsonObject ClientManagementModel::getContent(amnezia::Proto protocol)
{
QJsonObject clientsTable;
for (const auto &item : m_content) {
if (protocol == amnezia::Proto::OpenVpn) {
clientsTable[item.toJsonObject()["openvpnCertId"].toString()] = item.toJsonObject();
} else if (protocol == amnezia::Proto::WireGuard) {
clientsTable[item.toJsonObject()["wireguardPublicKey"].toString()] = item.toJsonObject();
}
}
return clientsTable;
}
int ClientManagementModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return static_cast<int>(m_content.size());
return static_cast<int>(m_clientsTable.size());
}
QVariant ClientManagementModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid() || index.row() < 0
|| index.row() >= static_cast<int>(m_content.size())) {
if (!index.isValid() || index.row() < 0 || index.row() >= static_cast<int>(m_clientsTable.size())) {
return QVariant();
}
if (role == NameRole) {
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();
auto client = m_clientsTable.at(index.row()).toObject();
auto userData = client.value("userData").toObject();
switch (role) {
case UserNameRole: return userData.value("clientName").toString();
case ContainerNameRole:
return ContainerProps::containerHumanNames().value(
static_cast<DockerContainer>(userData.value("container").toInt()));
}
return QVariant();
}
void ClientManagementModel::setData(const QModelIndex &index, QVariant data, int role)
ErrorCode ClientManagementModel::updateModel(DockerContainer container, ServerCredentials credentials)
{
if (!index.isValid() || index.row() < 0
|| index.row() >= static_cast<int>(m_content.size())) {
return;
ServerController serverController(m_settings);
ErrorCode error = ErrorCode::NoError;
QString stdOut;
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
stdOut += data + "\n";
return ErrorCode::NoError;
};
const QString clientsTableFile =
QString("/opt/amnezia/%1/clientsTable").arg(ContainerProps::containerTypeToString(container));
const QByteArray clientsTableString =
serverController.getTextFileFromContainer(container, credentials, clientsTableFile, &error);
if (error != ErrorCode::NoError) {
return error;
}
auto client = m_content[index.row()].toJsonObject();
if (role == NameRole) {
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;
beginResetModel();
m_clientsTable = QJsonDocument::fromJson(clientsTableString).array();
if (m_clientsTable.isEmpty()) {
int count = 0;
if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks
|| container == DockerContainer::Cloak) {
const QString getOpenVpnClientsList =
"sudo docker exec -i $CONTAINER_NAME bash -c 'ls /opt/amnezia/openvpn/pki/issued'";
QString script = serverController.replaceVars(getOpenVpnClientsList,
serverController.genVarsForScript(credentials, container));
error = serverController.runScript(credentials, script, cbReadStdOut);
if (error != ErrorCode::NoError) {
return error;
}
if (m_content[index.row()] != client) {
m_content[index.row()] = client;
emit dataChanged(index, index);
if (!stdOut.isEmpty()) {
QStringList certsIds = stdOut.split("\n", Qt::SkipEmptyParts);
certsIds.removeAll("AmneziaReq.crt");
for (auto &openvpnCertId : certsIds) {
openvpnCertId.replace(".crt", "");
if (!isClientExists(openvpnCertId)) {
QJsonObject client;
client["userId"] = openvpnCertId;
QJsonObject userData;
userData["clientName"] = QString("Client %1").arg(count);
userData["container"] = container;
client["userData"] = userData;
m_clientsTable.push_back(client);
count++;
}
}
}
} else if (container == DockerContainer::WireGuard || container == DockerContainer::Awg) {
const QString wireGuardConfigFile =
QString("opt/amnezia/%1/wg0.conf").arg(container == DockerContainer::WireGuard ? "wireguard" : "awg");
const QString wireguardConfigString =
serverController.getTextFileFromContainer(container, credentials, wireGuardConfigFile, &error);
if (error != ErrorCode::NoError) {
return error;
}
auto configLines = wireguardConfigString.split("\n", Qt::SkipEmptyParts);
QStringList wireguardKeys;
for (const auto &line : configLines) {
auto configPair = line.split(" = ", Qt::SkipEmptyParts);
if (configPair.front() == "PublicKey") {
wireguardKeys.push_back(configPair.back());
}
}
for (auto &wireguardKey : wireguardKeys) {
if (!isClientExists(wireguardKey)) {
QJsonObject client;
client["userId"] = wireguardKey;
QJsonObject userData;
userData["clientName"] = QString("Client %1").arg(count);
userData["container"] = container;
client["userData"] = userData;
m_clientsTable.push_back(client);
count++;
}
}
}
const QByteArray newClientsTableString = QJsonDocument(m_clientsTable).toJson();
if (clientsTableString != newClientsTableString) {
error = serverController.uploadTextFileToContainer(container, credentials, newClientsTableString,
clientsTableFile);
}
}
endResetModel();
return error;
}
bool ClientManagementModel::removeRows(int row)
bool ClientManagementModel::isClientExists(const QString &clientId)
{
beginRemoveRows(QModelIndex(), row, row);
m_content.removeAt(row);
endRemoveRows();
for (const QJsonValue &value : qAsConst(m_clientsTable)) {
if (value.isObject()) {
QJsonObject obj = value.toObject();
if (obj.contains("userId") && obj["userId"].toString() == clientId) {
return true;
}
}
}
return false;
}
ErrorCode ClientManagementModel::appendClient(const QString &clientId, const QString &clientName,
const DockerContainer container, ServerCredentials credentials)
{
ErrorCode error;
if (m_clientsTable.empty()) {
error = updateModel(container, credentials);
if (error != ErrorCode::NoError) {
return error;
}
for (int i = 0; i < m_clientsTable.size(); i++) {
if (m_clientsTable.at(i).toObject().value("userId") == (clientId)) {
error = renameClient(i, clientName, container, credentials);
if (error != ErrorCode::NoError) {
return error;
}
}
}
} else {
beginResetModel();
QJsonObject client;
client["userId"] = clientId;
QJsonObject userData;
userData["clientName"] = clientName;
userData["container"] = container;
client["userData"] = userData;
m_clientsTable.push_back(client);
endResetModel();
const QByteArray clientsTableString = QJsonDocument(m_clientsTable).toJson();
ServerController serverController(m_settings);
const QString clientsTableFile =
QString("/opt/amnezia/%1/clientsTable").arg(ContainerProps::containerTypeToString(container));
error = serverController.uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile);
}
return error;
}
ErrorCode ClientManagementModel::renameClient(const int row, const QString &clientName, const DockerContainer container,
ServerCredentials credentials)
{
auto client = m_clientsTable.at(row).toObject();
auto userData = client["userData"].toObject();
userData["clientName"] = clientName;
client["userData"] = userData;
m_clientsTable.replace(row, client);
emit dataChanged(index(row, 0), index(row, 0));
const QByteArray clientsTableString = QJsonDocument(m_clientsTable).toJson();
ServerController serverController(m_settings);
const QString clientsTableFile =
QString("/opt/amnezia/%1/clientsTable").arg(ContainerProps::containerTypeToString(container));
ErrorCode error =
serverController.uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile);
return error;
}
ErrorCode ClientManagementModel::revokeClient(const int row, const DockerContainer container,
ServerCredentials credentials)
{
if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks
|| container == DockerContainer::Cloak) {
return revokeOpenVpn(row, container, credentials);
} else if (container == DockerContainer::WireGuard || container == DockerContainer::Awg) {
return revokeWireGuard(row, container, credentials);
}
return ErrorCode::NoError;
}
ErrorCode ClientManagementModel::revokeOpenVpn(const int row, const DockerContainer container,
ServerCredentials credentials)
{
auto client = m_clientsTable.at(row).toObject();
QString clientId = client.value("userId").toString();
const QString getOpenVpnCertData = QString("sudo docker exec -i $CONTAINER_NAME bash -c '"
"cd /opt/amnezia/openvpn ;\\"
"easyrsa revoke %1 ;\\"
"easyrsa gen-crl ;\\"
"cp pki/crl.pem .'")
.arg(clientId);
ServerController serverController(m_settings);
const QString script =
serverController.replaceVars(getOpenVpnCertData, serverController.genVarsForScript(credentials, container));
ErrorCode error = serverController.runScript(credentials, script);
if (error != ErrorCode::NoError) {
return error;
}
beginRemoveRows(QModelIndex(), row, row);
m_clientsTable.removeAt(row);
endRemoveRows();
const QByteArray clientsTableString = QJsonDocument(m_clientsTable).toJson();
const QString clientsTableFile =
QString("/opt/amnezia/%1/clientsTable").arg(ContainerProps::containerTypeToString(container));
error = serverController.uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile);
if (error != ErrorCode::NoError) {
return error;
}
return ErrorCode::NoError;
}
ErrorCode ClientManagementModel::revokeWireGuard(const int row, const DockerContainer container,
ServerCredentials credentials)
{
ErrorCode error;
ServerController serverController(m_settings);
const QString wireGuardConfigFile =
QString("/opt/amnezia/%1/wg0.conf").arg(container == DockerContainer::WireGuard ? "wireguard" : "awg");
const QString wireguardConfigString =
serverController.getTextFileFromContainer(container, credentials, wireGuardConfigFile, &error);
if (error != ErrorCode::NoError) {
return error;
}
auto client = m_clientsTable.at(row).toObject();
QString clientId = client.value("userId").toString();
auto configSections = wireguardConfigString.split("[", Qt::SkipEmptyParts);
for (auto &section : configSections) {
if (section.contains(clientId)) {
configSections.removeOne(section);
break;
}
}
QString newWireGuardConfig = configSections.join("[");
newWireGuardConfig.insert(0, "[");
error = serverController.uploadTextFileToContainer(container, credentials, newWireGuardConfig, wireGuardConfigFile);
if (error != ErrorCode::NoError) {
return error;
}
beginRemoveRows(QModelIndex(), row, row);
m_clientsTable.removeAt(row);
endRemoveRows();
const QByteArray clientsTableString = QJsonDocument(m_clientsTable).toJson();
const QString clientsTableFile =
QString("/opt/amnezia/%1/clientsTable").arg(ContainerProps::containerTypeToString(container));
error = serverController.uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile);
if (error != ErrorCode::NoError) {
return error;
}
const QString script = "sudo docker exec -i $CONTAINER_NAME bash -c 'wg syncconf wg0 <(wg-quick strip %1)'";
error = serverController.runScript(
credentials,
serverController.replaceVars(script.arg(wireGuardConfigFile),
serverController.genVarsForScript(credentials, container)));
if (error != ErrorCode::NoError) {
return error;
}
return ErrorCode::NoError;
}
QHash<int, QByteArray> ClientManagementModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[NameRole] = "clientName";
roles[OpenVpnCertIdRole] = "openvpnCertId";
roles[OpenVpnCertDataRole] = "openvpnCertData";
roles[WireGuardPublicKey] = "wireguardPublicKey";
roles[UserNameRole] = "userName";
roles[ContainerNameRole] = "containerName";
return roles;
}

View file

@ -2,36 +2,62 @@
#define CLIENTMANAGEMENTMODEL_H
#include <QAbstractListModel>
#include <QJsonArray>
#include "protocols/protocols_defs.h"
#include "settings.h"
class ClientManagementModel : public QAbstractListModel
{
Q_OBJECT
public:
enum ClientRoles {
NameRole = Qt::UserRole + 1,
OpenVpnCertIdRole,
OpenVpnCertDataRole,
WireGuardPublicKey,
struct ClientManagementData
{
QString userId;
QJsonObject userData;
bool operator==(const ClientManagementData &r) const
{
return userId == r.userId;
}
bool operator==(const QString &otherUserId) const
{
return userId == otherUserId;
}
};
ClientManagementModel(QObject *parent = nullptr);
public:
enum Roles {
UserNameRole = Qt::UserRole + 1,
ContainerNameRole,
};
ClientManagementModel(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
void clearData();
void setContent(const QVector<QVariant> &data);
QJsonObject getContent(amnezia::Proto protocol);
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);
bool removeRows(int row);
public slots:
ErrorCode updateModel(DockerContainer container, ServerCredentials credentials);
ErrorCode appendClient(const QString &clientId, const QString &clientName, const DockerContainer container,
ServerCredentials credentials);
ErrorCode renameClient(const int row, const QString &userName, const DockerContainer container,
ServerCredentials credentials);
ErrorCode revokeClient(const int index, const DockerContainer container, ServerCredentials credentials);
protected:
QHash<int, QByteArray> roleNames() const override;
private:
QVector<QVariant> m_content;
bool isClientExists(const QString &clientId);
ErrorCode revokeOpenVpn(const int row, const DockerContainer container, ServerCredentials credentials);
ErrorCode revokeWireGuard(const int row, const DockerContainer container, ServerCredentials credentials);
QJsonArray m_clientsTable;
std::shared_ptr<Settings> m_settings;
};
#endif // CLIENTMANAGEMENTMODEL_H

View file

@ -145,6 +145,11 @@ QString ServersModel::getCurrentlyProcessedServerHostName()
return qvariant_cast<QString>(data(m_currentlyProcessedServerIndex, HostNameRole));
}
const ServerCredentials ServersModel::getCurrentlyProcessedServerCredentials()
{
return serverCredentials(m_currentlyProcessedServerIndex);
}
bool ServersModel::isDefaultServerCurrentlyProcessed()
{
return m_defaultServerIndex == m_currentlyProcessedServerIndex;

View file

@ -53,6 +53,7 @@ public slots:
int getCurrentlyProcessedServerIndex();
QString getCurrentlyProcessedServerHostName();
const ServerCredentials getCurrentlyProcessedServerCredentials();
void addServer(const QJsonObject &server);
void removeServer();

View file

@ -23,10 +23,22 @@ PageType {
WireGuard
}
signal revokeConfig(int index)
onRevokeConfig: function(index) {
PageController.showBusyIndicator(true)
ExportController.revokeConfig(index,
ContainersModel.getCurrentlyProcessedContainerIndex(),
ServersModel.getCurrentlyProcessedServerCredentials())
PageController.showBusyIndicator(false)
}
Connections {
target: ExportController
function onGenerateConfig(type) {
shareConnectionDrawer.headerText = qsTr("Connection to ") + serverSelector.text
shareConnectionDrawer.configContentHeaderText = qsTr("File with connection settings to ") + serverSelector.text
shareConnectionDrawer.needCloseButton = false
shareConnectionDrawer.open()
@ -34,7 +46,7 @@ PageType {
PageController.showBusyIndicator(true)
switch (type) {
case PageShare.ConfigType.AmneziaConnection: ExportController.generateConnectionConfig(); break;
case PageShare.ConfigType.AmneziaConnection: ExportController.generateConnectionConfig(userNameTextField.textFieldText); break;
case PageShare.ConfigType.AmneziaFullAccess: {
if (Qt.platform.os === "android") {
ExportController.generateFullAccessConfigAndroid();
@ -44,14 +56,14 @@ PageType {
break;
}
case PageShare.ConfigType.OpenVpn: {
ExportController.generateOpenVpnConfig();
ExportController.generateOpenVpnConfig(userNameTextField.textFieldText)
shareConnectionDrawer.configCaption = qsTr("Save OpenVPN config")
shareConnectionDrawer.configExtension = ".ovpn"
shareConnectionDrawer.configFileName = "amnezia_for_openvpn"
break;
}
case PageShare.ConfigType.WireGuard: {
ExportController.generateWireGuardConfig();
ExportController.generateWireGuardConfig(userNameTextField.textFieldText)
shareConnectionDrawer.configCaption = qsTr("Save WireGuard config")
shareConnectionDrawer.configExtension = ".conf"
shareConnectionDrawer.configFileName = "amnezia_for_wireguard"
@ -73,8 +85,6 @@ PageType {
}
}
property string fullConfigServerSelectorText
property string connectionServerSelectorText
property bool showContent: false
property bool shareButtonEnabled: true
property list<QtObject> connectionTypesModel: [
@ -147,20 +157,21 @@ PageType {
onClicked: {
accessTypeSelector.currentIndex = 0
serverSelector.text = root.connectionServerSelectorText
}
}
HorizontalRadioButton {
checked: root.currentIndex === 1
checked: accessTypeSelector.currentIndex === 1
implicitWidth: (root.width - 32) / 2
text: qsTr("Full access")
text: qsTr("Users")
onClicked: {
accessTypeSelector.currentIndex = 1
serverSelector.text = root.fullConfigServerSelectorText
root.shareButtonEnabled = true
PageController.showBusyIndicator(true)
ClientManagementModel.updateModel(ContainersModel.getCurrentlyProcessedContainerIndex(),
ServersModel.getCurrentlyProcessedServerCredentials())
PageController.showBusyIndicator(false)
}
}
}
@ -171,11 +182,25 @@ PageType {
Layout.topMargin: 24
Layout.bottomMargin: 24
text: accessTypeSelector.currentIndex === 0 ? qsTr("Share VPN access without the ability to manage the server") :
qsTr("Share access to server management. The user with whom you share full access to the server will be able to add and remove any protocols and services to the server, as well as change settings.")
visible: accessTypeSelector.currentIndex === 0
text: qsTr("Share VPN access without the ability to manage the server")
color: "#878B91"
}
TextFieldWithHeaderType {
id: userNameTextField
Layout.fillWidth: true
Layout.topMargin: 16
visible: accessTypeSelector.currentIndex === 0
headerText: qsTr("User name")
textFieldText: "New client"
checkEmptyText: true
}
DropDownType {
id: serverSelector
@ -217,10 +242,11 @@ PageType {
serverSelector.severSelectorIndexChanged()
}
if (accessTypeSelector.currentIndex !== 0) {
shareConnectionDrawer.headerText = qsTr("Accessing ") + serverSelector.text
shareConnectionDrawer.configContentHeaderText = qsTr("File with accessing settings to ") + serverSelector.text
}
//full access label
// if (accessTypeSelector.currentIndex !== 0) {
// shareConnectionDrawer.headerText = qsTr("Accessing ") + serverSelector.text
// shareConnectionDrawer.configContentHeaderText = qsTr("File with accessing settings to ") + serverSelector.text
// }
serverSelector.menuVisible = false
}
@ -231,8 +257,6 @@ PageType {
function handler() {
serverSelector.text = selectedText
root.fullConfigServerSelectorText = selectedText
root.connectionServerSelectorText = selectedText
ServersModel.currentlyProcessedIndex = proxyServersModel.mapToSource(currentIndex)
}
}
@ -241,8 +265,6 @@ PageType {
DropDownType {
id: protocolSelector
visible: accessTypeSelector.currentIndex === 0
Layout.fillWidth: true
Layout.topMargin: 16
@ -280,12 +302,6 @@ PageType {
protocolSelector.menuVisible = false
}
Component.onCompleted: {
if (accessTypeSelector.currentIndex === 0) {
handler()
}
}
Connections {
target: serverSelector
@ -304,13 +320,17 @@ PageType {
}
protocolSelector.text = selectedText
root.connectionServerSelectorText = serverSelector.text
shareConnectionDrawer.headerText = qsTr("Connection to ") + serverSelector.text
shareConnectionDrawer.configContentHeaderText = qsTr("File with connection settings to ") + serverSelector.text
ContainersModel.setCurrentlyProcessedContainerIndex(proxyContainersModel.mapToSource(currentIndex))
if (accessTypeSelector.currentIndex === 0) {
fillConnectionTypeModel()
} else {
PageController.showBusyIndicator(true)
ClientManagementModel.updateModel(ContainersModel.getCurrentlyProcessedContainerIndex(),
ServersModel.getCurrentlyProcessedServerCredentials())
PageController.showBusyIndicator(false)
}
}
function fillConnectionTypeModel() {
@ -378,17 +398,195 @@ PageType {
Layout.topMargin: 40
enabled: shareButtonEnabled
visible: accessTypeSelector.currentIndex === 0
text: qsTr("Share")
imageSource: "qrc:/images/controls/share-2.svg"
onClicked: {
if (accessTypeSelector.currentIndex === 0) {
ExportController.generateConfig(root.connectionTypesModel[exportTypeSelector.currentIndex].type)
} else {
ExportController.generateConfig(PageShare.ConfigType.AmneziaFullAccess)
}
}
Header2Type {
Layout.fillWidth: true
Layout.topMargin: 24
Layout.bottomMargin: 16
visible: accessTypeSelector.currentIndex === 1
headerText: qsTr("Users")
}
ListView {
id: usersListView
Layout.fillWidth: true
Layout.preferredHeight: childrenRect.height
visible: accessTypeSelector.currentIndex === 1
model: ClientManagementModel
clip: true
interactive: false
delegate: Item {
implicitWidth: usersListView.width
implicitHeight: delegateContent.implicitHeight
ColumnLayout {
id: delegateContent
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.rightMargin: -16
anchors.leftMargin: -16
LabelWithButtonType {
Layout.fillWidth: true
text: userName
descriptionText: containerName
rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() {
userInfoDrower.open()
}
}
DividerType {}
DrawerType {
id: userInfoDrower
width: root.width
height: root.height * 0.45
ColumnLayout {
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 16
anchors.leftMargin: 16
anchors.rightMargin: 16
spacing: 8
Header2Type {
Layout.fillWidth: true
Layout.bottomMargin: 24
headerText: userName
descriptionText: serverSelector.text + ", " + containerName
}
BasicButtonType {
Layout.fillWidth: true
Layout.topMargin: 24
defaultColor: "transparent"
hoveredColor: Qt.rgba(1, 1, 1, 0.08)
pressedColor: Qt.rgba(1, 1, 1, 0.12)
disabledColor: "#878B91"
textColor: "#D7D8DB"
borderWidth: 1
text: qsTr("Rename")
onClicked: function() {
clientNameEditDrawer.open()
}
DrawerType {
id: clientNameEditDrawer
width: root.width
height: root.height * 0.35
onVisibleChanged: {
if (clientNameEditDrawer.visible) {
clientName.textField.forceActiveFocus()
}
}
ColumnLayout {
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 16
anchors.leftMargin: 16
anchors.rightMargin: 16
TextFieldWithHeaderType {
id: clientName
Layout.fillWidth: true
headerText: qsTr("Client name")
textFieldText: userName
}
BasicButtonType {
Layout.fillWidth: true
text: qsTr("Save")
onClicked: {
if (clientName.textFieldText !== userName) {
PageController.showBusyIndicator(true)
ExportController.renameClient(index,
clientName.textFieldText,
ContainersModel.getCurrentlyProcessedContainerIndex(),
ServersModel.getCurrentlyProcessedServerCredentials())
PageController.showBusyIndicator(false)
clientNameEditDrawer.close()
}
}
}
}
}
}
BasicButtonType {
Layout.fillWidth: true
defaultColor: "transparent"
hoveredColor: Qt.rgba(1, 1, 1, 0.08)
pressedColor: Qt.rgba(1, 1, 1, 0.12)
disabledColor: "#878B91"
textColor: "#D7D8DB"
borderWidth: 1
text: qsTr("Revoke")
onClicked: function() {
questionDrawer.headerText = qsTr("Revoke the config for a user - ") + userName + "?"
questionDrawer.descriptionText = qsTr("The user will no longer be able to connect to your server.")
questionDrawer.yesButtonText = qsTr("Continue")
questionDrawer.noButtonText = qsTr("Cancel")
questionDrawer.yesButtonFunction = function() {
questionDrawer.close()
userInfoDrower.close()
root.revokeConfig(index)
}
questionDrawer.noButtonFunction = function() {
questionDrawer.close()
}
questionDrawer.open()
}
}
}
}
}
}
}
QuestionDrawer {
id: questionDrawer
}
}
}

View file

@ -227,12 +227,15 @@ QString VpnConnection::createVpnConfigurationForProto(int serverIndex, const Ser
configData = lastVpnConfig.value(proto);
configData = m_configurator->processConfigWithLocalSettings(serverIndex, container, proto, configData);
} else {
configData = m_configurator->genVpnProtocolConfig(credentials, container, containerConfig, proto, errorCode);
QString clientId;
configData = m_configurator->genVpnProtocolConfig(credentials, container, containerConfig, proto, clientId, errorCode);
if (errorCode && *errorCode) {
return "";
}
emit m_configurator->newVpnConfigCreated(clientId, "unnamed client", container, credentials);
QString configDataBeforeLocalProcessing = configData;
configData = m_configurator->processConfigWithLocalSettings(serverIndex, container, proto, configData);
@ -323,7 +326,7 @@ void VpnConnection::connectToVpn(int serverIndex, const ServerCredentials &crede
ErrorCode e = ErrorCode::NoError;
m_vpnConfiguration = createVpnConfiguration(serverIndex, credentials, container, containerConfig, &e);
emit newVpnConfigurationCreated();
if (e) {
emit connectionStateChanged(Vpn::ConnectionState::Error);
return;

View file

@ -79,8 +79,6 @@ signals:
void serviceIsNotReady();
void newVpnConfigurationCreated();
protected slots:
void onBytesChanged(quint64 receivedBytes, quint64 sentBytes);
void onConnectionStateChanged(Vpn::ConnectionState state);