|
|
@@ -1,3 +1,5 @@
|
|
|
+#include <cctype>
|
|
|
+#include <unordered_set>
|
|
|
#include "credential_controller.hpp"
|
|
|
#include "logging/logger.hpp"
|
|
|
|
|
|
@@ -7,11 +9,47 @@ using namespace credentials;
|
|
|
|
|
|
CredentialController::CredentialController(CredentialStore& credential_store,
|
|
|
auth::AccessControl& access,
|
|
|
+ storage::StorageClient& storage,
|
|
|
auth::AuthMiddleware& middleware)
|
|
|
: credential_store_(credential_store)
|
|
|
, access_(access)
|
|
|
+ , storage_(storage)
|
|
|
, middleware_(middleware) {}
|
|
|
|
|
|
+std::unordered_map<std::string, int> CredentialController::credentialUsage() {
|
|
|
+ std::unordered_map<std::string, int> usage;
|
|
|
+
|
|
|
+ storage::QueryOptions options;
|
|
|
+ options.page_size = 1000;
|
|
|
+ auto workflows = storage_.query("workflows", options);
|
|
|
+ if (workflows.failed()) return usage;
|
|
|
+
|
|
|
+ for (const auto& workflow : workflows.value().documents) {
|
|
|
+ // A credential id can appear in any node's config, under whatever key
|
|
|
+ // that node calls it - credentialId here, imapCredential there. Rather
|
|
|
+ // than keep a list of every key any node might use, which is a list
|
|
|
+ // that goes stale the moment somebody writes a node, the workflow is
|
|
|
+ // searched for the ids themselves.
|
|
|
+ const std::string text = workflow.dump();
|
|
|
+ std::unordered_set<std::string> counted;
|
|
|
+ size_t at = 0;
|
|
|
+ while ((at = text.find("cred_", at)) != std::string::npos) {
|
|
|
+ size_t end = at;
|
|
|
+ while (end < text.size() &&
|
|
|
+ (std::isalnum(static_cast<unsigned char>(text[end])) || text[end] == '_' ||
|
|
|
+ text[end] == '-')) {
|
|
|
+ ++end;
|
|
|
+ }
|
|
|
+ const std::string id = text.substr(at, end - at);
|
|
|
+ // Once per workflow, however many nodes in it use the credential -
|
|
|
+ // the question is how many workflows would break without it.
|
|
|
+ if (counted.insert(id).second) usage[id]++;
|
|
|
+ at = end;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return usage;
|
|
|
+}
|
|
|
+
|
|
|
void CredentialController::registerRoutes(httplib::Server& server) {
|
|
|
// List credentials - requires admin role
|
|
|
server.Get("/api/v1/credentials", [this](const httplib::Request& req, httplib::Response& res) {
|
|
|
@@ -81,10 +119,14 @@ void CredentialController::listCredentials(const httplib::Request& req, httplib:
|
|
|
// Only the projects this caller can reach. A credential is the most
|
|
|
// sensitive thing here - it is somebody's password to another system.
|
|
|
const auto reachable = access_.projectsFor(ctx);
|
|
|
+ const auto usage = credentialUsage();
|
|
|
nlohmann::json credentials_json = nlohmann::json::array();
|
|
|
for (const auto& cred : result.value()) {
|
|
|
if (!reachable.contains(cred.project_id)) continue;
|
|
|
- credentials_json.push_back(cred.toJson());
|
|
|
+ auto entry = cred.toJson();
|
|
|
+ const auto found = usage.find(cred.id);
|
|
|
+ entry["usedByWorkflows"] = found == usage.end() ? 0 : found->second;
|
|
|
+ credentials_json.push_back(entry);
|
|
|
}
|
|
|
|
|
|
sendJson(res, {{"credentials", credentials_json}});
|