Files
logos-cpp-sdk/cpp/module_proxy.cpp
T
Vedran 5c49a0d6a4 fix: redact sensitive data from debug logs (#18)
Remove logging of method arguments. It may contain private keys,
DB passwords, auth tokens, and token values. Log only method
names and argument counts for debugging.

- https://github.com/status-im/infra-logos/issues/1
2026-02-25 09:15:39 -05:00

487 lines
20 KiB
C++

#include "module_proxy.h"
#include <QDebug>
#include <QMetaObject>
#include <QMetaMethod>
#include <QMetaType>
#include <QJsonArray>
#include <QJsonObject>
#include <QStringList>
#include "../core/interface.h"
#include "logos_api.h"
#include "token_manager.h"
// Helper macro to simplify method invocation with return types
#define INVOKE_METHOD_WITH_RETURN(returnType, castType) \
do { \
castType* result = static_cast<castType*>(returnValue); \
switch (args.size()) { \
case 0: \
return QMetaObject::invokeMethod(module, methodNameCStr, Qt::DirectConnection, Q_RETURN_ARG(returnType, *result)); \
case 1: \
return QMetaObject::invokeMethod(module, methodNameCStr, Qt::DirectConnection, Q_RETURN_ARG(returnType, *result), scopedArgs[0].arg); \
case 2: \
return QMetaObject::invokeMethod(module, methodNameCStr, Qt::DirectConnection, Q_RETURN_ARG(returnType, *result), scopedArgs[0].arg, scopedArgs[1].arg); \
case 3: \
return QMetaObject::invokeMethod(module, methodNameCStr, Qt::DirectConnection, Q_RETURN_ARG(returnType, *result), scopedArgs[0].arg, scopedArgs[1].arg, scopedArgs[2].arg); \
case 4: \
return QMetaObject::invokeMethod(module, methodNameCStr, Qt::DirectConnection, Q_RETURN_ARG(returnType, *result), scopedArgs[0].arg, scopedArgs[1].arg, scopedArgs[2].arg, scopedArgs[3].arg); \
case 5: \
return QMetaObject::invokeMethod(module, methodNameCStr, Qt::DirectConnection, Q_RETURN_ARG(returnType, *result), scopedArgs[0].arg, scopedArgs[1].arg, scopedArgs[2].arg, scopedArgs[3].arg, scopedArgs[4].arg); \
default: \
qWarning() << "ModuleProxy: Currently supports 0-5 arguments. Got:" << args.size(); \
return false; \
} \
} while(0)
namespace {
class ScopedQArg {
public:
ScopedQArg(QMetaMethodArgument a, std::function<void(const void*)> d)
: arg(a), deleter(std::move(d)) {}
~ScopedQArg() {
if (deleter) {
deleter(arg.data);
}
}
ScopedQArg(ScopedQArg&& other)
: arg(std::move(other.arg)), deleter(std::move(other.deleter)) {
other.deleter = nullptr;
}
ScopedQArg& operator=(ScopedQArg&&) = delete;
ScopedQArg(const ScopedQArg&) = delete;
ScopedQArg& operator=(const ScopedQArg&) = delete;
QMetaMethodArgument arg;
private:
std::function<void(const void*)> deleter;
};
auto toScopedQArgs(const QVariantList& args)
{
auto scopedArgs = std::vector<ScopedQArg>{};
for (const auto& arg : args) {
switch (arg.typeId()) {
case QMetaType::Int: {
auto value = new int{arg.toInt()};
scopedArgs.emplace_back(
Q_ARG(int, *value),
[](const void* data) {
delete static_cast<const int*>(data);
}
);
break;
}
case QMetaType::QStringList: {
auto value = new QStringList{arg.toStringList()};
scopedArgs.emplace_back(
Q_ARG(QStringList, *value),
[](const void* data) {
delete static_cast<const QStringList*>(data);
}
);
break;
}
case QMetaType::QByteArray: {
auto value = new QByteArray{arg.toByteArray()};
scopedArgs.emplace_back(
Q_ARG(QByteArray, *value),
[](const void* data) {
delete static_cast<const QByteArray*>(data);
}
);
break;
}
case QMetaType::QUrl: {
auto value = new QUrl{arg.toUrl()};
scopedArgs.emplace_back(
Q_ARG(QUrl, *value),
[](const void* data) {
delete static_cast<const QUrl*>(data);
}
);
break;
}
case QMetaType::Bool: {
auto value = new bool{arg.toBool()};
scopedArgs.emplace_back(
Q_ARG(bool, *value),
[](const void* data) {
delete static_cast<const bool*>(data);
}
);
break;
}
case QMetaType::QString:
default: {
auto value = new QString{arg.toString()};
scopedArgs.emplace_back(
Q_ARG(QString, *value),
[](const void* data) {
delete static_cast<const QString*>(data);
}
);
break;
}
}
}
return scopedArgs;
}
// Helper method to invoke methods with different return types and argument counts
bool invokeMethodByArgCount(QObject *module, const QString& methodName, const QVariantList& args, void* returnValue, const char* returnTypeName)
{
// Store the UTF-8 data to ensure it stays in scope
QByteArray methodNameBytes = methodName.toUtf8();
const char* methodNameCStr = methodNameBytes.constData();
auto scopedArgs = toScopedQArgs(args);
if (returnValue == nullptr) {
// Void method - no return value
switch (args.size()) {
case 0:
return QMetaObject::invokeMethod(module, methodNameCStr, Qt::DirectConnection);
case 1:
return QMetaObject::invokeMethod(module, methodNameCStr, Qt::DirectConnection, scopedArgs[0].arg);
case 2:
return QMetaObject::invokeMethod(module, methodNameCStr, Qt::DirectConnection, scopedArgs[0].arg, scopedArgs[1].arg);
case 3:
return QMetaObject::invokeMethod(module, methodNameCStr, Qt::DirectConnection, scopedArgs[0].arg, scopedArgs[1].arg, scopedArgs[2].arg);
case 4:
return QMetaObject::invokeMethod(module, methodNameCStr, Qt::DirectConnection, scopedArgs[0].arg, scopedArgs[1].arg, scopedArgs[2].arg, scopedArgs[3].arg);
case 5:
return QMetaObject::invokeMethod(module, methodNameCStr, Qt::DirectConnection, scopedArgs[0].arg, scopedArgs[1].arg, scopedArgs[2].arg, scopedArgs[3].arg, scopedArgs[4].arg);
default:
qWarning() << "ModuleProxy: Currently supports 0-5 arguments. Got:" << args.size();
return false;
}
} else if (strcmp(returnTypeName, "bool") == 0) {
qDebug() << "ModuleProxy: invokeMethodByArgCount - bool case with" << args.size() << "arguments";
INVOKE_METHOD_WITH_RETURN(bool, bool);
} else if (strcmp(returnTypeName, "int") == 0) {
INVOKE_METHOD_WITH_RETURN(int, int);
} else if (strcmp(returnTypeName, "QString") == 0) {
INVOKE_METHOD_WITH_RETURN(QString, QString);
} else if (strcmp(returnTypeName, "LogosResult") == 0) {
INVOKE_METHOD_WITH_RETURN(LogosResult, LogosResult);
} else if (strcmp(returnTypeName, "QVariant") == 0) {
INVOKE_METHOD_WITH_RETURN(QVariant, QVariant);
} else if (strcmp(returnTypeName, "QJsonArray") == 0) {
INVOKE_METHOD_WITH_RETURN(QJsonArray, QJsonArray);
} else if (strcmp(returnTypeName, "QStringList") == 0) {
INVOKE_METHOD_WITH_RETURN(QStringList, QStringList);
} else {
qWarning() << "ModuleProxy: Unsupported return type in invokeMethodByArgCount:" << returnTypeName;
return false;
}
}
}
ModuleProxy::ModuleProxy(QObject* module, QObject* parent)
: QObject(parent)
, m_module(module)
{
// Connect to the wrapped object's eventResponse signal to forward events
if (m_module) {
QObject::connect(m_module, SIGNAL(eventResponse(QString, QVariantList)),
this, SIGNAL(eventResponse(QString, QVariantList)));
qDebug() << "ModuleProxy: Connected to wrapped object's eventResponse signal";
}
}
ModuleProxy::~ModuleProxy()
{
qDebug() << "ModuleProxy: Destroyed for module:" << m_module;
}
bool ModuleProxy::saveToken(const QString& from_module_name, const QString& token)
{
if (from_module_name.isEmpty()) {
qWarning() << "ModuleProxy: Cannot save token with empty module name";
return false;
}
if (token.isEmpty()) {
qWarning() << "ModuleProxy: Cannot save empty token for module:" << from_module_name;
return false;
}
qDebug() << "ModuleProxy: Saving token for module:" << from_module_name;
m_tokens[from_module_name] = token;
qDebug() << "ModuleProxy: Token saved successfully. Total tokens stored:" << m_tokens.size();
return true;
}
QVariant ModuleProxy::callRemoteMethod(const QString& authToken, const QString& methodName, const QVariantList& args)
{
if (!m_module) {
qWarning() << "ModuleProxy: Cannot call method on null module:" << methodName;
return QVariant();
}
if (methodName.isEmpty()) {
qWarning() << "ModuleProxy: Method name cannot be empty";
return QVariant();
}
// TODO: review this. note this method is part of ModuleProxy
if (methodName == "getPluginMethods" && args.isEmpty()) {
qDebug() << "ModuleProxy: Handling getPluginMethods() directly";
return QVariant(getPluginMethods());
}
qDebug() << "ModuleProxy: Auth token received:" << authToken;
qDebug() << "ModuleProxy: Calling method" << methodName << "on module" << m_module << "with args:" << args;
PluginInterface* pluginInterface = qobject_cast<PluginInterface*>(m_module);
if (!pluginInterface) {
qWarning() << "ModuleProxy: Module is not a PluginInterface";
return false;
}
// now print the name
qDebug() << "ModuleProxy: PluginInterface name:" << pluginInterface->name();
// get Logos API
LogosAPI* logosAPI = pluginInterface->logosAPI;
if (!logosAPI) {
qWarning() << "ModuleProxy: LogosAPI not available";
return false;
}
// get TokenManager
TokenManager* tokenManager = logosAPI->getTokenManager();
if (!tokenManager) {
qWarning() << "ModuleProxy: TokenManager not available";
return false;
}
// print keys vand values for debug purposes
QList<QString> keys = tokenManager->getTokenKeys();
for (const QString& key : keys) {
qDebug() << "ModuleProxy: Token key:" << key;
}
// check if authToken is valid
if (authToken.isEmpty()) {
qWarning() << "ModuleProxy: Auth token is empty";
return QVariant();
}
// check if authToken is stored in tokenManager
if (!tokenManager->getToken(authToken).isEmpty()) {
qDebug() << "ERROR: ===================== getToken(authToken) is INVALID =====================";
qWarning() << "ModuleProxy: Auth token not found in stored tokens";
qDebug() << "ERROR: ===================== getToken(authToken) is INVALID =====================";
return QVariant();
} else {
qDebug() << "VALID: ===================== getToken(authToken) is VALID =====================";
}
// Each createArgument() call now generates its own unique GUID
// Find the method to get its return type
const QMetaObject* metaObject = m_module->metaObject();
int methodIndex = -1;
qDebug() << "ModuleProxy: Looking for method" << methodName << "with" << args.size() << "arguments";
qDebug() << "ModuleProxy: Available methods in" << metaObject->className() << ":";
// Debug: List all available methods
for (int i = 0; i < metaObject->methodCount(); ++i) {
QMetaMethod method = metaObject->method(i);
qDebug() << " Method" << i << ":" << method.name() << "with" << method.parameterCount() << "parameters, return type:" << method.returnMetaType().name();
}
// Find the method with matching name and argument count
for (int i = 0; i < metaObject->methodCount(); ++i) {
QMetaMethod method = metaObject->method(i);
if (method.name() == methodName && method.parameterCount() == args.size()) {
methodIndex = i;
qDebug() << "ModuleProxy: Found matching method at index" << i;
break;
}
}
if (methodIndex == -1) {
qWarning() << "ModuleProxy: Method not found:" << methodName << "with" << args.size() << "arguments";
return QVariant();
}
QMetaMethod method = metaObject->method(methodIndex);
QMetaType returnType = method.returnMetaType();
qDebug() << "ModuleProxy: Method signature:" << method.methodSignature();
qDebug() << "ModuleProxy: Parameter types:";
for (int i = 0; i < method.parameterCount(); ++i) {
qDebug() << " Param" << i << ":" << method.parameterMetaType(i).name();
}
// Handle different return types
bool success = false;
QVariant result;
if (returnType == QMetaType::fromType<void>()) {
// Void method - no return value expected
success = invokeMethodByArgCount(m_module, methodName, args, nullptr, nullptr);
if (success) {
result = QVariant(true); // Return true to indicate success
}
} else if (returnType == QMetaType::fromType<bool>()) {
// Bool return type
qDebug() << "ModuleProxy: Invoking bool method" << methodName;
bool boolResult = false;
success = invokeMethodByArgCount(m_module, methodName, args, &boolResult, "bool");
qDebug() << "ModuleProxy: Bool method invocation result:" << success << "value:" << boolResult;
if (success) {
result = QVariant(boolResult);
}
} else if (returnType == QMetaType::fromType<int>()) {
// Int return type
int intResult = 0;
success = invokeMethodByArgCount(m_module, methodName, args, &intResult, "int");
if (success) {
result = QVariant(intResult);
}
} else if (returnType == QMetaType::fromType<QString>()) {
// QString return type
QString stringResult;
success = invokeMethodByArgCount(m_module, methodName, args, &stringResult, "QString");
if (success) {
result = QVariant(stringResult);
}
}
else if (returnType == QMetaType::fromType<LogosResult>()) {
// LogosResult return type
LogosResult logosResult;
success = invokeMethodByArgCount(m_module, methodName, args, &logosResult, "LogosResult");
if (success) {
result = QVariant::fromValue(logosResult);
}
}
else if (returnType == QMetaType::fromType<QVariant>()) {
// QVariant return type
QVariant variantResult;
success = invokeMethodByArgCount(m_module, methodName, args, &variantResult, "QVariant");
if (success) {
result = variantResult;
}
} else if (returnType == QMetaType::fromType<QJsonArray>()) {
// QJsonArray return type
qDebug() << "ModuleProxy: Invoking QJsonArray method" << methodName;
QJsonArray jsonArrayResult;
success = invokeMethodByArgCount(m_module, methodName, args, &jsonArrayResult, "QJsonArray");
qDebug() << "ModuleProxy: QJsonArray method invocation result:" << success << "array size:" << jsonArrayResult.size();
if (success) {
result = QVariant(jsonArrayResult);
}
} else if (returnType == QMetaType::fromType<QStringList>()) {
// QStringList return type
qDebug() << "ModuleProxy: Invoking QStringList method" << methodName;
QStringList stringListResult;
success = invokeMethodByArgCount(m_module, methodName, args, &stringListResult, "QStringList");
qDebug() << "ModuleProxy: QStringList method invocation result:" << success << "list size:" << stringListResult.size();
if (success) {
result = QVariant(stringListResult);
}
} else {
qWarning() << "ModuleProxy: Unsupported return type:" << returnType.name() << "for method:" << methodName;
return QVariant();
}
if (!success) {
qWarning() << "ModuleProxy: Failed to invoke method" << methodName << "on module" << m_module;
return QVariant();
}
// Note: Argument cleanup is now handled automatically by each createArgument() call's unique GUID
qDebug() << "ModuleProxy: Successfully called method" << methodName << "on module" << m_module;
return result;
}
bool ModuleProxy::informModuleToken(const QString& authToken, const QString& moduleName, const QString& token)
{
Q_UNUSED(authToken) // Authentication token validation can be added later
// cast m_module to PluginInterface
PluginInterface* pluginInterface = qobject_cast<PluginInterface*>(m_module);
if (!pluginInterface) {
qWarning() << "ModuleProxy: Module is not a PluginInterface";
return false;
}
// now print the name
qDebug() << "ModuleProxy: PluginInterface name:" << pluginInterface->name();
// get Logos API
LogosAPI* logosAPI = pluginInterface->logosAPI;
if (!logosAPI) {
qWarning() << "ModuleProxy: LogosAPI not available";
return false;
}
// get TokenManager
TokenManager* tokenManager = logosAPI->getTokenManager();
if (!tokenManager) {
qWarning() << "ModuleProxy: TokenManager not available";
return false;
}
// save token
qDebug() << "ModuleProxy: Saving token for module:" << moduleName << "with token:" << token;
tokenManager->saveToken(moduleName, token);
qDebug() << "ModuleProxy: Token saved successfully";
return true;
}
QJsonArray ModuleProxy::getPluginMethods()
{
QJsonArray methodsArray;
const QMetaObject* metaObject = m_module->metaObject();
for (int i = 0; i < metaObject->methodCount(); ++i) {
QMetaMethod method = metaObject->method(i);
if (method.enclosingMetaObject() != metaObject) {
continue;
}
QJsonObject methodObj;
methodObj["signature"] = QString::fromUtf8(method.methodSignature());
methodObj["name"] = QString::fromUtf8(method.name());
methodObj["returnType"] = QString::fromUtf8(method.typeName());
methodObj["isInvokable"] = method.isValid() && (method.methodType() == QMetaMethod::Method || method.methodType() == QMetaMethod::Slot);
if (method.parameterCount() > 0) {
QJsonArray params;
for (int p = 0; p < method.parameterCount(); ++p) {
QJsonObject paramObj;
paramObj["type"] = QString::fromUtf8(method.parameterTypeName(p));
QByteArrayList paramNames = method.parameterNames();
if (p < paramNames.size() && !paramNames.at(p).isEmpty()) {
paramObj["name"] = QString::fromUtf8(paramNames.at(p));
} else {
paramObj["name"] = QString("param%1").arg(p);
}
params.append(paramObj);
}
methodObj["parameters"] = params;
}
methodsArray.append(methodObj);
}
return methodsArray;
}
// Include MOC for template instantiation
#include "moc_module_proxy.cpp"