mirror of
https://github.com/logos-co/logos-protocol.git
synced 2026-08-31 05:51:08 +00:00
capability_module is the last legacy Qt Q_INVOKABLE provider, and it cannot
become an ordinary `interface: universal` module while the two things it does
have no C entry point: reading the token store, and pushing a token to an
ARBITRARY target. This adds both, plus the grant that gates them. Purely
additive — no existing symbol changes behaviour.
lp_token_keys() the module names THIS image's TokenManager
holds. NULL means REFUSED, never "empty" — a
granted call with no tokens answers "[]", and a
known-caller gate needs to tell those apart.
lp_inform_module_token_to() routes to LogosAPIClient::informModuleToken_module,
the 5-arg form. Note the existing
lp_inform_module_token is the WRONG DIRECTION
for this: it reaches a consumer path that
hardcodes requestObject("capability_module"),
i.e. core -> capability, not capability ->
target. That 5-arg method had no C entry point.
lp_grant_host_services() sets the in-image grant over the closed set
{token_registry, token_delivery}. Replaces
rather than merges; NULL/""/"[]" clears. An
unknown name is rejected wholesale and leaves
the existing grant untouched, so a typo can
never silently drop a service.
Why the gate is per-IMAGE, which looks like an odd choice until it doesn't:
the host binary and a module's cdylib each link their own copy of this library,
so they have separate process-global state. A gate "simplified" into the host
would be checked against state the calling image can never set, and would read
as ungranted forever. The grant therefore crosses the module-impl C ABI the
same way the auth token already does — hence the logos_module_grant_host_services
declaration added to logos_module_impl.h, whose generated body and host-side
call land in logos-cpp-sdk and logos-module-loader-qt respectively.
MINOR 2 -> 3; MAJOR unchanged, so the equal-MAJOR compatibility rule is
unaffected. 387/387 tests pass, including 6 new ones covering both gates
closed, both opened, clearing re-closing them, and the unknown-name rejection.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
207 lines
6.2 KiB
C++
207 lines
6.2 KiB
C++
#ifndef TOKEN_MANAGER_H
|
|
#define TOKEN_MANAGER_H
|
|
|
|
#include <QObject>
|
|
#include <QString>
|
|
#include <QHash>
|
|
#include <QMutex>
|
|
#include <QByteArray>
|
|
#include <QCryptographicHash>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "logos_shared_api.h"
|
|
|
|
/**
|
|
* @brief Render a capability/auth token safe to write to logs.
|
|
*
|
|
* Tokens gate all cross-module RPC and are accepted by value (see
|
|
* ModuleProxy::isAuthorized), so a raw token recovered from a log line is
|
|
* directly replayable. This collapses a token to a non-reversible, non-
|
|
* replayable fingerprint — a fixed prefix plus the first bytes of its SHA-256 —
|
|
* suitable for correlating log lines without exposing the secret. The
|
|
* "redacted:" prefix signals to anyone reading the log that this is a
|
|
* deliberately non-replayable fingerprint, not a truncated real token. Empty
|
|
* tokens render as "<none>" so missing-token cases stay greppable.
|
|
*
|
|
* Always pass tokens through this before logging; never log the raw value.
|
|
*/
|
|
inline QString redactToken(const QString& token)
|
|
{
|
|
if (token.isEmpty()) {
|
|
return QStringLiteral("<none>");
|
|
}
|
|
const QByteArray digest =
|
|
QCryptographicHash::hash(token.toUtf8(), QCryptographicHash::Sha256);
|
|
return QStringLiteral("redacted:") + QString::fromLatin1(digest.toHex().left(8)) + QStringLiteral("…");
|
|
}
|
|
|
|
/**
|
|
* @brief TokenManager provides a singleton interface for managing authentication tokens
|
|
*
|
|
* This class manages a collection of tokens identified by keys, providing thread-safe
|
|
* access to store, retrieve, and manage tokens throughout the application lifecycle.
|
|
*
|
|
* LOGOS_SHARED_API: the singleton below is a function-local static, so it is one
|
|
* per copy of the code. On PE that means one per IMAGE unless the consumer
|
|
* imports it from liblogos_core.dll — which is exactly the token-invisibility
|
|
* bug described in logos_shared_api.h. Off Windows, and inside the provider,
|
|
* this expands to nothing.
|
|
*/
|
|
class LOGOS_SHARED_API TokenManager : public QObject
|
|
{
|
|
Q_OBJECT
|
|
|
|
public:
|
|
/**
|
|
* @brief Get the singleton instance of TokenManager
|
|
* @return TokenManager& Reference to the singleton instance
|
|
*/
|
|
static TokenManager& instance();
|
|
|
|
/**
|
|
* @brief Save a token with the given key
|
|
* @param key The identifier for the token
|
|
* @param token The token value to store
|
|
*/
|
|
void saveToken(const QString& key, const QString& token);
|
|
|
|
/**
|
|
* @brief Save a token — const char* overload (resolves ambiguity, delegates to QString)
|
|
*/
|
|
void saveToken(const char* key, const char* token)
|
|
{ saveToken(QString(key), QString(token)); }
|
|
|
|
/**
|
|
* @brief Save a token with the given key (std::string overload)
|
|
*/
|
|
void saveToken(const std::string& key, const std::string& token);
|
|
|
|
/**
|
|
* @brief Retrieve a token by key
|
|
* @param key The identifier for the token
|
|
* @return QString The token value, or empty string if not found
|
|
*/
|
|
QString getToken(const QString& key) const;
|
|
|
|
/**
|
|
* @brief Retrieve a token — const char* overload (resolves ambiguity, delegates to QString)
|
|
*/
|
|
QString getToken(const char* key) const
|
|
{ return getToken(QString(key)); }
|
|
|
|
/**
|
|
* @brief Retrieve a token by key (std::string overload)
|
|
* @return std::string The token value, or empty string if not found
|
|
*/
|
|
std::string getToken(const std::string& key) const;
|
|
|
|
/**
|
|
* @brief Check if a token exists for the given key
|
|
* @param key The identifier to check
|
|
* @return bool True if token exists, false otherwise
|
|
*/
|
|
bool hasToken(const QString& key) const;
|
|
|
|
/**
|
|
* @brief hasToken — const char* overload (resolves ambiguity, delegates to QString)
|
|
*/
|
|
bool hasToken(const char* key) const
|
|
{ return hasToken(QString(key)); }
|
|
|
|
/**
|
|
* @brief Check if a token exists for the given key (std::string overload)
|
|
*/
|
|
bool hasToken(const std::string& key) const;
|
|
|
|
/**
|
|
* @brief Remove a token by key
|
|
* @param key The identifier for the token to remove
|
|
* @return bool True if token was removed, false if it didn't exist
|
|
*/
|
|
bool removeToken(const QString& key);
|
|
|
|
/**
|
|
* @brief removeToken — const char* overload (resolves ambiguity, delegates to QString)
|
|
*/
|
|
bool removeToken(const char* key)
|
|
{ return removeToken(QString(key)); }
|
|
|
|
/**
|
|
* @brief Remove a token by key (std::string overload)
|
|
*/
|
|
bool removeToken(const std::string& key);
|
|
|
|
/**
|
|
* @brief Clear all tokens
|
|
*/
|
|
void clearAllTokens();
|
|
|
|
/**
|
|
* @brief Get all token keys
|
|
* @return QList<QString> List of all token keys
|
|
*/
|
|
QList<QString> getTokenKeys() const;
|
|
|
|
/**
|
|
* @brief Get all token keys (std::string flavour)
|
|
*
|
|
* Named rather than overloaded because C++ cannot overload on return type
|
|
* alone. Exists for the Qt-free callers — lp_token_keys() and, through it,
|
|
* any language SDK — so the conversion lives here next to the store rather
|
|
* than being retyped at each boundary.
|
|
*/
|
|
std::vector<std::string> getTokenKeysStd() const;
|
|
|
|
/**
|
|
* @brief Get the number of stored tokens
|
|
* @return int Number of tokens stored
|
|
*/
|
|
int tokenCount() const;
|
|
|
|
signals:
|
|
/**
|
|
* @brief Emitted when a token is saved
|
|
* @param key The key of the saved token
|
|
*/
|
|
void tokenSaved(const QString& key);
|
|
|
|
/**
|
|
* @brief Emitted when a token is removed
|
|
* @param key The key of the removed token
|
|
*/
|
|
void tokenRemoved(const QString& key);
|
|
|
|
/**
|
|
* @brief Emitted when all tokens are cleared
|
|
*/
|
|
void allTokensCleared();
|
|
|
|
private:
|
|
/**
|
|
* @brief Private constructor for singleton pattern
|
|
* @param parent Parent QObject
|
|
*/
|
|
explicit TokenManager(QObject *parent = nullptr);
|
|
|
|
/**
|
|
* @brief Private destructor
|
|
*/
|
|
~TokenManager();
|
|
|
|
// Delete copy constructor and assignment operator to enforce singleton
|
|
TokenManager(const TokenManager&) = delete;
|
|
TokenManager& operator=(const TokenManager&) = delete;
|
|
|
|
/**
|
|
* @brief Hash map storing tokens by key
|
|
*/
|
|
QHash<QString, QString> m_tokens;
|
|
|
|
/**
|
|
* @brief Mutex for thread-safe access to tokens
|
|
*/
|
|
mutable QMutex m_mutex;
|
|
};
|
|
|
|
#endif // TOKEN_MANAGER_H
|