From f60afbe59b1b3618ccecf0f8d7ae099ac0f450ab Mon Sep 17 00:00:00 2001
From: Amaury Chamayou <amchamay@microsoft.com>
Date: Mon, 17 Jun 2024 11:24:18 +0100
Subject: [PATCH] Audit in programmability sample (#6258)

---
 samples/apps/programmability/audit_info.h     | 40 +++++++++++++++++++
 .../apps/programmability/programmability.cpp  | 37 +++++++++++++----
 2 files changed, 70 insertions(+), 7 deletions(-)
 create mode 100644 samples/apps/programmability/audit_info.h

diff --git a/samples/apps/programmability/audit_info.h b/samples/apps/programmability/audit_info.h
new file mode 100644
index 000000000000..e485a39c2224
--- /dev/null
+++ b/samples/apps/programmability/audit_info.h
@@ -0,0 +1,40 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the Apache 2.0 License.
+
+#pragma once
+#include "ccf/ds/json.h"
+#include "ccf/entity_id.h"
+
+#include <vector>
+
+namespace programmabilityapp
+{
+  enum class AuditInputFormat
+  {
+    COSE = 0,
+    JSON = 1
+  };
+  DECLARE_JSON_ENUM(
+    AuditInputFormat,
+    {{AuditInputFormat::COSE, "COSE"}, {AuditInputFormat::JSON, "JSON"}});
+
+  enum class AuditInputContent
+  {
+    BUNDLE = 0,
+    OPTIONS = 1
+  };
+  DECLARE_JSON_ENUM(
+    AuditInputContent,
+    {{AuditInputContent::BUNDLE, "BUNDLE"},
+     {AuditInputContent::OPTIONS, "OPTIONS"}});
+
+  struct AuditInfo
+  {
+    AuditInputFormat format;
+    AuditInputContent content;
+    ccf::UserId user_id;
+  };
+
+  DECLARE_JSON_TYPE(AuditInfo)
+  DECLARE_JSON_REQUIRED_FIELDS(AuditInfo, format, content, user_id)
+}
\ No newline at end of file
diff --git a/samples/apps/programmability/programmability.cpp b/samples/apps/programmability/programmability.cpp
index f2b2800ccd95..b93aff4c9d45 100644
--- a/samples/apps/programmability/programmability.cpp
+++ b/samples/apps/programmability/programmability.cpp
@@ -2,6 +2,7 @@
 // Licensed under the Apache 2.0 License.
 
 // CCF
+#include "audit_info.h"
 #include "ccf/app_interface.h"
 #include "ccf/common_auth_policies.h"
 #include "ccf/ds/hash.h"
@@ -19,7 +20,10 @@ using namespace nlohmann;
 namespace programmabilityapp
 {
   using RecordsMap = kv::Map<std::string, std::vector<uint8_t>>;
+  using AuditInputValue = kv::Value<std::vector<uint8_t>>;
+  using AuditInfoValue = kv::Value<AuditInfo>;
   static constexpr auto PRIVATE_RECORDS = "programmability.records";
+  static constexpr auto CUSTOM_ENDPOINTS_NAMESPACE = "public:custom_endpoints";
 
   // This sample shows the features of DynamicJSEndpointRegistry. This sample
   // adds a PUT /app/custom_endpoints, which calls install_custom_endpoints(),
@@ -49,17 +53,18 @@ namespace programmabilityapp
       return std::nullopt;
     }
 
-    std::span<const uint8_t> get_body(ccf::endpoints::EndpointContext& ctx)
+    std::pair<AuditInputFormat, std::span<const uint8_t>> get_body(
+      ccf::endpoints::EndpointContext& ctx)
     {
       if (
         const auto* cose_ident =
           ctx.try_get_caller<ccf::UserCOSESign1AuthnIdentity>())
       {
-        return cose_ident->content;
+        return {AuditInputFormat::COSE, cose_ident->content};
       }
       else
       {
-        return ctx.rpc_ctx->get_request_body();
+        return {AuditInputFormat::JSON, ctx.rpc_ctx->get_request_body()};
       }
     }
 
@@ -67,8 +72,8 @@ namespace programmabilityapp
     ProgrammabilityHandlers(ccfapp::AbstractNodeContext& context) :
       ccf::js::DynamicJSEndpointRegistry(
         context,
-        "public:custom_endpoints" // Internal KV space will be under
-                                  // public:custom_endpoints.*
+        CUSTOM_ENDPOINTS_NAMESPACE // Internal KV space will be under
+                                   // public:custom_endpoints.*
       )
     {
       openapi_info.title = "CCF Programmabilit App";
@@ -223,10 +228,19 @@ namespace programmabilityapp
         }
         // End of Authorization Check
 
-        const auto bundle = get_body(ctx);
+        const auto [format, bundle] = get_body(ctx);
         const auto j = nlohmann::json::parse(bundle.begin(), bundle.end());
         const auto parsed_bundle = j.get<ccf::js::Bundle>();
 
+        // Make operation auditable by writing user-supplied
+        // document to the ledger
+        auto audit_input = ctx.tx.template rw<AuditInputValue>(
+          fmt::format("{}.audit.input", CUSTOM_ENDPOINTS_NAMESPACE));
+        audit_input->put(ctx.rpc_ctx->get_request_body());
+        auto audit_info = ctx.tx.template rw<AuditInfoValue>(
+          fmt::format("{}.audit.info", CUSTOM_ENDPOINTS_NAMESPACE));
+        audit_info->put({format, AuditInputContent::BUNDLE, user_id.value()});
+
         result = install_custom_endpoints_v1(ctx.tx, parsed_bundle);
         if (result != ccf::ApiResult::OK)
         {
@@ -377,7 +391,7 @@ namespace programmabilityapp
           // - Convert current options to JSON
           auto j_options = nlohmann::json(options);
 
-          const auto body = get_body(ctx);
+          const auto [format, body] = get_body(ctx);
           // - Parse argument as JSON body
           const auto arg_body = nlohmann::json::parse(body.begin(), body.end());
 
@@ -389,6 +403,15 @@ namespace programmabilityapp
           // - Parse patched options from JSON
           options = j_options.get<ccf::JSRuntimeOptions>();
 
+          // Make operation auditable by writing user-supplied
+          // document to the ledger
+          auto audit = ctx.tx.template rw<AuditInputValue>(
+            fmt::format("{}.audit.input", CUSTOM_ENDPOINTS_NAMESPACE));
+          audit->put(ctx.rpc_ctx->get_request_body());
+          auto audit_info = ctx.tx.template rw<AuditInfoValue>(
+            fmt::format("{}.audit.info", CUSTOM_ENDPOINTS_NAMESPACE));
+          audit_info->put({format, AuditInputContent::BUNDLE, user_id.value()});
+
           result = set_js_runtime_options_v1(ctx.tx, options);
           if (result != ccf::ApiResult::OK)
           {