зеркало из https://github.com/microsoft/CCF.git
Basic programmability sample (#6201)
This commit is contained in:
Родитель
bdac0c49c2
Коммит
4a201a6f61
|
@ -350,6 +350,7 @@ set(CCF_JS_SOURCES
|
|||
${CCF_DIR}/src/js/extensions/ccf/network.cpp
|
||||
${CCF_DIR}/src/js/extensions/ccf/node.cpp
|
||||
${CCF_DIR}/src/js/extensions/ccf/rpc.cpp
|
||||
${CCF_DIR}/src/js/extensions/ccf/request.cpp
|
||||
)
|
||||
|
||||
if(COMPILE_TARGET STREQUAL "sgx")
|
||||
|
@ -590,9 +591,7 @@ elseif(COMPILE_TARGET STREQUAL "virtual")
|
|||
set(JS_SNP_ATTESTATION_VIRTUAL js_snp_attestation.virtual)
|
||||
endif()
|
||||
|
||||
set(JS_GENERIC_SOURCES ${CCF_DIR}/src/apps/js_generic/js_generic_base.cpp
|
||||
${CCF_DIR}/src/apps/js_generic/request_extension.cpp
|
||||
)
|
||||
set(JS_GENERIC_SOURCES ${CCF_DIR}/src/apps/js_generic/js_generic_base.cpp)
|
||||
if(COMPILE_TARGET STREQUAL "sgx")
|
||||
add_enclave_library(js_generic_base.enclave ${JS_GENERIC_SOURCES})
|
||||
target_link_libraries(js_generic_base.enclave PUBLIC ccf.enclave)
|
||||
|
@ -1424,6 +1423,11 @@ if(BUILD_TESTS)
|
|||
${CMAKE_SOURCE_DIR}/samples/apps/logging/js
|
||||
)
|
||||
|
||||
add_e2e_test(
|
||||
NAME programmability
|
||||
PYTHON_SCRIPT ${CMAKE_SOURCE_DIR}/tests/programmability.py
|
||||
)
|
||||
|
||||
# This test uses large requests (so too slow for SAN)
|
||||
if(NOT SAN)
|
||||
add_e2e_test(
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the Apache 2.0 License.
|
||||
#pragma once
|
||||
|
||||
#include "ccf/ds/json.h"
|
||||
#include "ccf/endpoint.h"
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
namespace ccf::js
|
||||
{
|
||||
struct Metadata
|
||||
{
|
||||
std::map<
|
||||
std::string,
|
||||
std::map<std::string, ccf::endpoints::EndpointProperties>>
|
||||
endpoints;
|
||||
};
|
||||
DECLARE_JSON_TYPE(Metadata);
|
||||
DECLARE_JSON_REQUIRED_FIELDS(Metadata, endpoints);
|
||||
|
||||
struct Bundle
|
||||
{
|
||||
std::map<std::string, std::string> modules;
|
||||
Metadata metadata;
|
||||
};
|
||||
|
||||
DECLARE_JSON_TYPE(Bundle);
|
||||
DECLARE_JSON_REQUIRED_FIELDS(Bundle, modules, metadata);
|
||||
|
||||
struct BundleWrapper
|
||||
{
|
||||
Bundle bundle;
|
||||
};
|
||||
|
||||
DECLARE_JSON_TYPE(BundleWrapper);
|
||||
DECLARE_JSON_REQUIRED_FIELDS(BundleWrapper, bundle);
|
||||
}
|
|
@ -65,4 +65,11 @@ namespace ccf
|
|||
{
|
||||
ds::json::fill_schema<ClaimsDigest::Digest>(schema);
|
||||
}
|
||||
|
||||
static ClaimsDigest empty_claims()
|
||||
{
|
||||
ClaimsDigest cd;
|
||||
cd.set(ClaimsDigest::Digest::Representation());
|
||||
return cd;
|
||||
}
|
||||
}
|
|
@ -28,7 +28,39 @@ namespace ccf::endpoints
|
|||
return fmt::format("{} {}", verb.c_str(), uri_path);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
namespace kv::serialisers
|
||||
{
|
||||
template <>
|
||||
struct BlitSerialiser<ccf::endpoints::EndpointKey>
|
||||
{
|
||||
static SerialisedEntry to_serialised(
|
||||
const ccf::endpoints::EndpointKey& endpoint_key)
|
||||
{
|
||||
auto str =
|
||||
fmt::format("{} {}", endpoint_key.verb.c_str(), endpoint_key.uri_path);
|
||||
return SerialisedEntry(str.begin(), str.end());
|
||||
}
|
||||
|
||||
static ccf::endpoints::EndpointKey from_serialised(
|
||||
const SerialisedEntry& data)
|
||||
{
|
||||
std::string str{data.begin(), data.end()};
|
||||
auto i = str.find(' ');
|
||||
if (i == std::string::npos)
|
||||
{
|
||||
throw std::logic_error("invalid encoding of endpoint key");
|
||||
}
|
||||
auto verb = str.substr(0, i);
|
||||
auto uri_path = str.substr(i + 1);
|
||||
return {uri_path, verb};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
namespace ccf::endpoints
|
||||
{
|
||||
DECLARE_JSON_TYPE(EndpointKey);
|
||||
DECLARE_JSON_REQUIRED_FIELDS(EndpointKey, uri_path, verb);
|
||||
|
||||
|
@ -467,4 +499,4 @@ struct formatter<ccf::endpoints::ForwardingRequired>
|
|||
return format_to(ctx.out(), "{}", s);
|
||||
}
|
||||
};
|
||||
FMT_END_NAMESPACE
|
||||
FMT_END_NAMESPACE
|
||||
|
|
|
@ -0,0 +1,150 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the Apache 2.0 License.
|
||||
|
||||
#include "ccf/endpoint.h"
|
||||
#include "ccf/endpoints/authentication/all_of_auth.h"
|
||||
|
||||
namespace ccf
|
||||
{
|
||||
using NamedAuthPolicies =
|
||||
std::unordered_map<std::string, std::shared_ptr<ccf::AuthnPolicy>>;
|
||||
|
||||
static inline NamedAuthPolicies& auth_policies_by_name()
|
||||
{
|
||||
static NamedAuthPolicies policies;
|
||||
if (policies.empty())
|
||||
{
|
||||
policies.emplace(
|
||||
ccf::UserCertAuthnPolicy::SECURITY_SCHEME_NAME,
|
||||
ccf::user_cert_auth_policy);
|
||||
|
||||
policies.emplace(
|
||||
ccf::MemberCertAuthnPolicy::SECURITY_SCHEME_NAME,
|
||||
ccf::member_cert_auth_policy);
|
||||
|
||||
policies.emplace(
|
||||
ccf::JwtAuthnPolicy::SECURITY_SCHEME_NAME, ccf::jwt_auth_policy);
|
||||
|
||||
policies.emplace(
|
||||
ccf::UserCOSESign1AuthnPolicy::SECURITY_SCHEME_NAME,
|
||||
ccf::user_cose_sign1_auth_policy);
|
||||
|
||||
policies.emplace(
|
||||
ccf::EmptyAuthnPolicy::SECURITY_SCHEME_NAME, ccf::empty_auth_policy);
|
||||
}
|
||||
|
||||
return policies;
|
||||
}
|
||||
|
||||
static inline std::shared_ptr<ccf::AuthnPolicy> get_policy_by_name(
|
||||
const std::string& name)
|
||||
{
|
||||
auto& policies = auth_policies_by_name();
|
||||
auto it = policies.find(name);
|
||||
if (it == policies.end())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return it->second;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static inline constexpr char const* get_policy_name_from_ident(const T*)
|
||||
{
|
||||
if constexpr (std::is_same_v<T, ccf::UserCertAuthnIdentity>)
|
||||
{
|
||||
return ccf::UserCertAuthnPolicy::SECURITY_SCHEME_NAME;
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, ccf::MemberCertAuthnIdentity>)
|
||||
{
|
||||
return ccf::MemberCertAuthnPolicy::SECURITY_SCHEME_NAME;
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, ccf::JwtAuthnIdentity>)
|
||||
{
|
||||
return ccf::JwtAuthnPolicy::SECURITY_SCHEME_NAME;
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, ccf::UserCOSESign1AuthnIdentity>)
|
||||
{
|
||||
return ccf::UserCOSESign1AuthnPolicy::SECURITY_SCHEME_NAME;
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, ccf::MemberCOSESign1AuthnIdentity>)
|
||||
{
|
||||
return ccf::MemberCOSESign1AuthnPolicy::SECURITY_SCHEME_NAME;
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, ccf::EmptyAuthnIdentity>)
|
||||
{
|
||||
return ccf::EmptyAuthnPolicy::SECURITY_SCHEME_NAME;
|
||||
}
|
||||
else
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void instantiate_authn_policies(
|
||||
ccf::endpoints::EndpointDefinition& endpoint)
|
||||
{
|
||||
for (const auto& policy_desc : endpoint.properties.authn_policies)
|
||||
{
|
||||
if (policy_desc.is_string())
|
||||
{
|
||||
const auto policy_name = policy_desc.get<std::string>();
|
||||
auto policy = get_policy_by_name(policy_name);
|
||||
if (policy == nullptr)
|
||||
{
|
||||
throw std::logic_error(
|
||||
fmt::format("Unknown auth policy: {}", policy_name));
|
||||
}
|
||||
endpoint.authn_policies.push_back(std::move(policy));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (policy_desc.is_object())
|
||||
{
|
||||
const auto it = policy_desc.find("all_of");
|
||||
if (it != policy_desc.end())
|
||||
{
|
||||
if (it.value().is_array())
|
||||
{
|
||||
std::vector<std::shared_ptr<ccf::AuthnPolicy>>
|
||||
constituent_policies;
|
||||
for (const auto& val : it.value())
|
||||
{
|
||||
if (!val.is_string())
|
||||
{
|
||||
constituent_policies.clear();
|
||||
break;
|
||||
}
|
||||
|
||||
const auto policy_name = val.get<std::string>();
|
||||
auto policy = get_policy_by_name(policy_name);
|
||||
if (policy == nullptr)
|
||||
{
|
||||
throw std::logic_error(
|
||||
fmt::format("Unknown auth policy: {}", policy_name));
|
||||
}
|
||||
constituent_policies.push_back(std::move(policy));
|
||||
}
|
||||
|
||||
if (!constituent_policies.empty())
|
||||
{
|
||||
endpoint.authn_policies.push_back(
|
||||
std::make_shared<ccf::AllOfAuthnPolicy>(
|
||||
constituent_policies));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Any failure in above checks falls through to this detailed error.
|
||||
throw std::logic_error(fmt::format(
|
||||
"Unsupported auth policy. Policies must be either a string, or an "
|
||||
"object containing an \"all_of\" key with list-of-strings value. "
|
||||
"Unsupported value: {}",
|
||||
policy_desc.dump()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,11 +2,11 @@
|
|||
// Licensed under the Apache 2.0 License.
|
||||
#pragma once
|
||||
|
||||
#include "ccf/js/core/runtime.h"
|
||||
#include "ccf/js/core/wrapped_value.h"
|
||||
#include "ccf/js/extensions/extension_interface.h"
|
||||
#include "ccf/js/tx_access.h"
|
||||
#include "ccf/pal/locking.h"
|
||||
#include "js/core/runtime.h"
|
||||
#include "js/core/wrapped_value.h"
|
||||
#include "js/extensions/extension_interface.h"
|
||||
#include "js/tx_access.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <quickjs/quickjs-exports.h>
|
|
@ -2,7 +2,7 @@
|
|||
// Licensed under the Apache 2.0 License.
|
||||
#pragma once
|
||||
|
||||
#include "kv/kv_types.h"
|
||||
#include "ccf/tx.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <quickjs/quickjs.h>
|
|
@ -2,8 +2,8 @@
|
|||
// Licensed under the Apache 2.0 License.
|
||||
#pragma once
|
||||
|
||||
#include "js/core/context.h"
|
||||
#include "js/core/wrapped_value.h"
|
||||
#include "ccf/js/core/context.h"
|
||||
#include "ccf/js/core/wrapped_value.h"
|
||||
|
||||
#include <quickjs/quickjs.h>
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
// Licensed under the Apache 2.0 License.
|
||||
#pragma once
|
||||
|
||||
#include "js/core/constants.h"
|
||||
#include "ccf/js/core/constants.h"
|
||||
|
||||
#include <quickjs/quickjs.h>
|
||||
#include <string>
|
|
@ -3,7 +3,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "ccf/base_endpoint_registry.h"
|
||||
#include "js/extensions/extension_interface.h"
|
||||
#include "ccf/js/extensions/extension_interface.h"
|
||||
|
||||
namespace ccf::js::extensions
|
||||
{
|
|
@ -2,7 +2,7 @@
|
|||
// Licensed under the Apache 2.0 License.
|
||||
#pragma once
|
||||
|
||||
#include "js/extensions/extension_interface.h"
|
||||
#include "ccf/js/extensions/extension_interface.h"
|
||||
|
||||
namespace ccf::js::extensions
|
||||
{
|
|
@ -2,7 +2,7 @@
|
|||
// Licensed under the Apache 2.0 License.
|
||||
#pragma once
|
||||
|
||||
#include "js/extensions/extension_interface.h"
|
||||
#include "ccf/js/extensions/extension_interface.h"
|
||||
|
||||
namespace ccf::js::extensions
|
||||
{
|
|
@ -2,8 +2,8 @@
|
|||
// Licensed under the Apache 2.0 License.
|
||||
#pragma once
|
||||
|
||||
#include "ccf/js/extensions/extension_interface.h"
|
||||
#include "ccf/tx.h"
|
||||
#include "js/extensions/extension_interface.h"
|
||||
|
||||
namespace ccf::js::extensions
|
||||
{
|
|
@ -3,7 +3,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "ccf/historical_queries_interface.h"
|
||||
#include "js/extensions/extension_interface.h"
|
||||
#include "ccf/js/extensions/extension_interface.h"
|
||||
|
||||
#include <quickjs/quickjs.h>
|
||||
|
|
@ -2,8 +2,8 @@
|
|||
// Licensed under the Apache 2.0 License.
|
||||
#pragma once
|
||||
|
||||
#include "ccf/js/extensions/extension_interface.h"
|
||||
#include "ccf/node/host_processes_interface.h"
|
||||
#include "js/extensions/extension_interface.h"
|
||||
|
||||
namespace ccf::js::extensions
|
||||
{
|
|
@ -2,8 +2,8 @@
|
|||
// Licensed under the Apache 2.0 License.
|
||||
#pragma once
|
||||
|
||||
#include "ccf/js/extensions/extension_interface.h"
|
||||
#include "ccf/tx.h"
|
||||
#include "js/extensions/extension_interface.h"
|
||||
|
||||
#include <memory>
|
||||
|
|
@ -4,11 +4,11 @@
|
|||
|
||||
#include "ccf/base_endpoint_registry.h"
|
||||
#include "ccf/endpoint_context.h"
|
||||
#include "ccf/js/core/wrapped_value.h"
|
||||
#include "ccf/js/extensions/extension_interface.h"
|
||||
#include "ccf/rpc_context.h"
|
||||
#include "js/core/wrapped_value.h"
|
||||
#include "js/extensions/extension_interface.h"
|
||||
|
||||
namespace ccfapp
|
||||
namespace ccf::js::extensions
|
||||
{
|
||||
/**
|
||||
**/
|
|
@ -2,8 +2,8 @@
|
|||
// Licensed under the Apache 2.0 License.
|
||||
#pragma once
|
||||
|
||||
#include "ccf/js/extensions/extension_interface.h"
|
||||
#include "ccf/rpc_context.h"
|
||||
#include "js/extensions/extension_interface.h"
|
||||
|
||||
namespace ccf::js::extensions
|
||||
{
|
|
@ -2,8 +2,8 @@
|
|||
// Licensed under the Apache 2.0 License.
|
||||
#pragma once
|
||||
|
||||
#include "js/extensions/extension_interface.h"
|
||||
#include "js/tx_access.h"
|
||||
#include "ccf/js/extensions/extension_interface.h"
|
||||
#include "ccf/js/tx_access.h"
|
||||
|
||||
#include <string_view>
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
// Licensed under the Apache 2.0 License.
|
||||
#pragma once
|
||||
|
||||
#include "js/extensions/extension_interface.h"
|
||||
#include "ccf/js/extensions/extension_interface.h"
|
||||
|
||||
namespace ccf::js::extensions
|
||||
{
|
|
@ -5,13 +5,21 @@
|
|||
#include "ccf/ds/logger.h"
|
||||
#include "ccf/service/tables/modules.h"
|
||||
#include "ccf/tx.h"
|
||||
#include "ccf/version.h"
|
||||
|
||||
#include <quickjs/quickjs.h>
|
||||
|
||||
namespace ccf::js
|
||||
{
|
||||
static inline js::core::JSWrappedValue load_app_module(
|
||||
JSContext* ctx, const char* module_name, kv::Tx* tx)
|
||||
JSContext* ctx,
|
||||
const char* module_name,
|
||||
kv::Tx* tx,
|
||||
const std::string& modules_map = ccf::Tables::MODULES,
|
||||
const std::string& modules_quickjs_bytecode_map =
|
||||
ccf::Tables::MODULES_QUICKJS_BYTECODE,
|
||||
const std::string& modules_quickjs_version_map =
|
||||
ccf::Tables::MODULES_QUICKJS_VERSION)
|
||||
{
|
||||
js::core::Context& jsctx = *(js::core::Context*)JS_GetContextOpaque(ctx);
|
||||
|
||||
|
@ -26,20 +34,20 @@ namespace ccf::js
|
|||
auto loaded_module = jsctx.get_module_from_cache(module_name_quickjs);
|
||||
if (loaded_module.has_value())
|
||||
{
|
||||
LOG_TRACE_FMT("Using module from interpreter cache '{}'", module_name_kv);
|
||||
CCF_APP_TRACE("Using module from interpreter cache '{}'", module_name_kv);
|
||||
return loaded_module.value();
|
||||
}
|
||||
|
||||
const auto modules = tx->ro<ccf::Modules>(ccf::Tables::MODULES);
|
||||
const auto modules = tx->ro<ccf::Modules>(modules_map);
|
||||
|
||||
std::optional<std::vector<uint8_t>> bytecode;
|
||||
const auto modules_quickjs_bytecode = tx->ro<ccf::ModulesQuickJsBytecode>(
|
||||
ccf::Tables::MODULES_QUICKJS_BYTECODE);
|
||||
const auto modules_quickjs_bytecode =
|
||||
tx->ro<ccf::ModulesQuickJsBytecode>(modules_quickjs_bytecode_map);
|
||||
bytecode = modules_quickjs_bytecode->get(module_name_kv);
|
||||
if (bytecode)
|
||||
{
|
||||
auto modules_quickjs_version = tx->ro<ccf::ModulesQuickJsVersion>(
|
||||
ccf::Tables::MODULES_QUICKJS_VERSION);
|
||||
auto modules_quickjs_version =
|
||||
tx->ro<ccf::ModulesQuickJsVersion>(modules_quickjs_version_map);
|
||||
if (modules_quickjs_version->get() != std::string(ccf::quickjs_version))
|
||||
bytecode = std::nullopt;
|
||||
}
|
||||
|
@ -48,7 +56,7 @@ namespace ccf::js
|
|||
|
||||
if (!bytecode)
|
||||
{
|
||||
LOG_TRACE_FMT("Loading module '{}'", module_name_kv);
|
||||
CCF_APP_TRACE("Loading module '{}'", module_name_kv);
|
||||
|
||||
auto module = modules->get(module_name_kv);
|
||||
auto& js = module.value();
|
||||
|
@ -76,7 +84,7 @@ namespace ccf::js
|
|||
}
|
||||
else
|
||||
{
|
||||
LOG_TRACE_FMT("Loading module from bytecode cache '{}'", module_name_kv);
|
||||
CCF_APP_TRACE("Loading module from bytecode cache '{}'", module_name_kv);
|
||||
|
||||
module_val = jsctx.read_object(
|
||||
bytecode->data(), bytecode->size(), JS_READ_OBJ_BYTECODE);
|
||||
|
@ -112,7 +120,7 @@ namespace ccf::js
|
|||
}
|
||||
}
|
||||
|
||||
LOG_TRACE_FMT("Adding module to interpreter cache '{}'", module_name_kv);
|
||||
CCF_APP_TRACE("Adding module to interpreter cache '{}'", module_name_kv);
|
||||
jsctx.load_module_to_cache(module_name_quickjs, module_val);
|
||||
|
||||
return module_val;
|
|
@ -2,9 +2,9 @@
|
|||
// Licensed under the Apache 2.0 License.
|
||||
#pragma once
|
||||
|
||||
#include "ccf/claims_digest.h"
|
||||
#include "ccf/research/grpc_status.h"
|
||||
#include "ccf/rpc_context.h"
|
||||
#include "endpoints/grpc/grpc_status.h"
|
||||
#include "node/rpc/claims.h"
|
||||
|
||||
namespace ccf
|
||||
{
|
|
@ -13,19 +13,47 @@
|
|||
#define FMT_HEADER_ONLY
|
||||
#include <fmt/format.h>
|
||||
|
||||
using namespace std;
|
||||
// Custom Endpoints
|
||||
#include "custom_endpoints/registry.h"
|
||||
|
||||
using namespace nlohmann;
|
||||
|
||||
namespace basicapp
|
||||
{
|
||||
using RecordsMap = kv::Map<string, std::vector<uint8_t>>;
|
||||
using RecordsMap = kv::Map<std::string, std::vector<uint8_t>>;
|
||||
static constexpr auto PRIVATE_RECORDS = "records";
|
||||
|
||||
class BasicHandlers : public ccf::UserEndpointRegistry
|
||||
// By subclassing CustomJSEndpointRegistry, this application gains the ability
|
||||
// execute custom JavaScript endpoints, and exposes the ability to install
|
||||
// them via install_custom_endpoints().
|
||||
// This sample also adds a PUT /app/custom_endpoints that enables a user
|
||||
// for which user_data["isAdmin"] is true to install custom JavaScript
|
||||
// endpoints. The JavaScript code for these endpoints is stored in the
|
||||
// internal KV store under a namespace configured in the second argument to
|
||||
// the constructor. PUT /app/custom_endpoints is logically
|
||||
// equivalent to passing a set_js_app proposal in governance, except the
|
||||
// application resides in the application space.
|
||||
//
|
||||
// Known limitations:
|
||||
//
|
||||
// No auditability yet, COSE Sign1 auth is mandated, but the signature is not
|
||||
// stored.
|
||||
// No support for historical endpoints yet.
|
||||
// No support for import from external modules.
|
||||
//
|
||||
// Additional functionality compared to set_js_app:
|
||||
//
|
||||
// The KV namespace can be private, to keep the application confidential if
|
||||
// desired.
|
||||
class BasicHandlers : public basicapp::CustomJSEndpointRegistry
|
||||
{
|
||||
public:
|
||||
BasicHandlers(ccfapp::AbstractNodeContext& context) :
|
||||
ccf::UserEndpointRegistry(context)
|
||||
basicapp::CustomJSEndpointRegistry(
|
||||
context,
|
||||
"public:custom_endpoints" // Internal KV space will be under
|
||||
// public:custom_endpoints.*
|
||||
)
|
||||
{
|
||||
openapi_info.title = "CCF Basic App";
|
||||
openapi_info.description =
|
||||
|
@ -105,6 +133,57 @@ namespace basicapp
|
|||
};
|
||||
make_endpoint("/records", HTTP_POST, post, {ccf::user_cert_auth_policy})
|
||||
.install();
|
||||
|
||||
auto put_custom_endpoints = [this](ccf::endpoints::EndpointContext& ctx) {
|
||||
const auto& caller_identity =
|
||||
ctx.template get_caller<ccf::UserCOSESign1AuthnIdentity>();
|
||||
|
||||
// Authorization Check
|
||||
nlohmann::json user_data = nullptr;
|
||||
auto result =
|
||||
get_user_data_v1(ctx.tx, caller_identity.user_id, user_data);
|
||||
if (result == ccf::ApiResult::InternalError)
|
||||
{
|
||||
ctx.rpc_ctx->set_error(
|
||||
HTTP_STATUS_INTERNAL_SERVER_ERROR,
|
||||
ccf::errors::InternalError,
|
||||
fmt::format(
|
||||
"Failed to get user data for user {}: {}",
|
||||
caller_identity.user_id,
|
||||
ccf::api_result_to_str(result)));
|
||||
return;
|
||||
}
|
||||
const auto is_admin_it = user_data.find("isAdmin");
|
||||
|
||||
// Not every user gets to define custom endpoints, only users with
|
||||
// isAdmin
|
||||
if (
|
||||
!user_data.is_object() || is_admin_it == user_data.end() ||
|
||||
!is_admin_it.value().get<bool>())
|
||||
{
|
||||
ctx.rpc_ctx->set_error(
|
||||
HTTP_STATUS_FORBIDDEN,
|
||||
ccf::errors::AuthorizationFailed,
|
||||
"Only admins may access this endpoint.");
|
||||
return;
|
||||
}
|
||||
// End of Authorization Check
|
||||
|
||||
const auto j = nlohmann::json::parse(
|
||||
caller_identity.content.begin(), caller_identity.content.end());
|
||||
const auto wrapper = j.get<ccf::js::BundleWrapper>();
|
||||
|
||||
install_custom_endpoints(ctx, wrapper);
|
||||
ctx.rpc_ctx->set_response_status(HTTP_STATUS_NO_CONTENT);
|
||||
};
|
||||
|
||||
make_endpoint(
|
||||
"/custom_endpoints",
|
||||
HTTP_PUT,
|
||||
put_custom_endpoints,
|
||||
{ccf::user_cose_sign1_auth_policy})
|
||||
.set_auto_schema<ccf::js::BundleWrapper, void>()
|
||||
.install();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,673 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the Apache 2.0 License.
|
||||
|
||||
// CCF
|
||||
#include "ccf/app_interface.h"
|
||||
#include "ccf/common_auth_policies.h"
|
||||
#include "ccf/ds/hash.h"
|
||||
#include "ccf/http_query.h"
|
||||
#include "ccf/json_handler.h"
|
||||
#include "ccf/version.h"
|
||||
|
||||
#include <charconv>
|
||||
#define FMT_HEADER_ONLY
|
||||
#include <fmt/format.h>
|
||||
|
||||
// Custom Endpoints
|
||||
#include "ccf/bundle.h"
|
||||
#include "ccf/endpoint.h"
|
||||
#include "ccf/endpoints/authentication/js.h"
|
||||
#include "ccf/js/core/context.h"
|
||||
#include "ccf/js/core/wrapped_property_enum.h"
|
||||
#include "ccf/js/extensions/ccf/consensus.h"
|
||||
#include "ccf/js/extensions/ccf/converters.h"
|
||||
#include "ccf/js/extensions/ccf/crypto.h"
|
||||
#include "ccf/js/extensions/ccf/historical.h"
|
||||
#include "ccf/js/extensions/ccf/host.h"
|
||||
#include "ccf/js/extensions/ccf/kv.h"
|
||||
#include "ccf/js/extensions/ccf/request.h"
|
||||
#include "ccf/js/extensions/ccf/rpc.h"
|
||||
#include "ccf/js/extensions/console.h"
|
||||
#include "ccf/js/extensions/math/random.h"
|
||||
#include "ccf/js/modules.h"
|
||||
#include "ccf/node/rpc_context_impl.h"
|
||||
#include "js/interpreter_cache_interface.h"
|
||||
|
||||
using namespace nlohmann;
|
||||
|
||||
namespace basicapp
|
||||
{
|
||||
struct CustomJSEndpoint : public ccf::endpoints::Endpoint
|
||||
{};
|
||||
|
||||
class CustomJSEndpointRegistry : public ccf::UserEndpointRegistry
|
||||
{
|
||||
private:
|
||||
std::shared_ptr<ccf::js::AbstractInterpreterCache> interpreter_cache =
|
||||
nullptr;
|
||||
std::string modules_map;
|
||||
std::string metadata_map;
|
||||
std::string interpreter_flush_map;
|
||||
std::string modules_quickjs_version_map;
|
||||
std::string modules_quickjs_bytecode_map;
|
||||
|
||||
public:
|
||||
CustomJSEndpointRegistry(
|
||||
ccfapp::AbstractNodeContext& context,
|
||||
const std::string& kv_prefix_ = "public:custom_endpoints") :
|
||||
ccf::UserEndpointRegistry(context),
|
||||
modules_map(fmt::format("{}.modules", kv_prefix_)),
|
||||
metadata_map(fmt::format("{}.metadata", kv_prefix_)),
|
||||
interpreter_flush_map(fmt::format("{}.interpreter_flush", kv_prefix_)),
|
||||
modules_quickjs_version_map(
|
||||
fmt::format("{}.modules_quickjs_version", kv_prefix_)),
|
||||
modules_quickjs_bytecode_map(
|
||||
fmt::format("{}.modules_quickjs_bytecode", kv_prefix_))
|
||||
{
|
||||
interpreter_cache =
|
||||
context.get_subsystem<ccf::js::AbstractInterpreterCache>();
|
||||
if (interpreter_cache == nullptr)
|
||||
{
|
||||
throw std::logic_error(
|
||||
"Unexpected: Could not access AbstractInterpreterCache subsytem");
|
||||
}
|
||||
|
||||
// Install dependency-less (ie reusable) extensions on interpreters _at
|
||||
// creation_, rather than on every run
|
||||
ccf::js::extensions::Extensions extensions;
|
||||
// override Math.random
|
||||
extensions.emplace_back(
|
||||
std::make_shared<ccf::js::extensions::MathRandomExtension>());
|
||||
// add console.[debug|log|...]
|
||||
extensions.emplace_back(
|
||||
std::make_shared<ccf::js::extensions::ConsoleExtension>());
|
||||
// add ccf.[strToBuf|bufToStr|...]
|
||||
extensions.emplace_back(
|
||||
std::make_shared<ccf::js::extensions::ConvertersExtension>());
|
||||
// add ccf.crypto.*
|
||||
extensions.emplace_back(
|
||||
std::make_shared<ccf::js::extensions::CryptoExtension>());
|
||||
// add ccf.consensus.*
|
||||
extensions.emplace_back(
|
||||
std::make_shared<ccf::js::extensions::ConsensusExtension>(this));
|
||||
// add ccf.host.*
|
||||
extensions.emplace_back(
|
||||
std::make_shared<ccf::js::extensions::HostExtension>(
|
||||
context.get_subsystem<ccf::AbstractHostProcesses>().get()));
|
||||
// add ccf.historical.*
|
||||
extensions.emplace_back(
|
||||
std::make_shared<ccf::js::extensions::HistoricalExtension>(
|
||||
&context.get_historical_state()));
|
||||
|
||||
interpreter_cache->set_interpreter_factory(
|
||||
[extensions](ccf::js::TxAccess access) {
|
||||
auto interpreter = std::make_shared<ccf::js::core::Context>(access);
|
||||
|
||||
for (auto extension : extensions)
|
||||
{
|
||||
interpreter->add_extension(extension);
|
||||
}
|
||||
|
||||
return interpreter;
|
||||
});
|
||||
}
|
||||
|
||||
void install_custom_endpoints(
|
||||
ccf::endpoints::EndpointContext& ctx,
|
||||
const ccf::js::BundleWrapper& wrapper)
|
||||
{
|
||||
auto endpoints =
|
||||
ctx.tx.template rw<ccf::endpoints::EndpointsMap>(metadata_map);
|
||||
endpoints->clear();
|
||||
for (const auto& [url, methods] : wrapper.bundle.metadata.endpoints)
|
||||
{
|
||||
for (const auto& [method, metadata] : methods)
|
||||
{
|
||||
std::string method_upper = method;
|
||||
nonstd::to_upper(method_upper);
|
||||
const auto key = ccf::endpoints::EndpointKey{url, method_upper};
|
||||
endpoints->put(key, metadata);
|
||||
}
|
||||
}
|
||||
|
||||
auto modules = ctx.tx.template rw<ccf::Modules>(modules_map);
|
||||
modules->clear();
|
||||
for (const auto& [name, module] : wrapper.bundle.modules)
|
||||
{
|
||||
modules->put(fmt::format("/{}", name), module);
|
||||
}
|
||||
|
||||
// Trigger interpreter flush, in case interpreter reuse
|
||||
// is enabled for some endpoints
|
||||
auto interpreter_flush =
|
||||
ctx.tx.template rw<ccf::InterpreterFlush>(interpreter_flush_map);
|
||||
interpreter_flush->put(true);
|
||||
|
||||
// Refresh app bytecode
|
||||
ccf::js::core::Context jsctx(ccf::js::TxAccess::APP_RW);
|
||||
jsctx.runtime().set_runtime_options(
|
||||
&ctx.tx, ccf::js::core::RuntimeLimitsPolicy::NO_LOWER_THAN_DEFAULTS);
|
||||
JS_SetModuleLoaderFunc(
|
||||
jsctx.runtime(), nullptr, ccf::js::js_app_module_loader, &ctx.tx);
|
||||
|
||||
auto quickjs_version =
|
||||
ctx.tx.wo<ccf::ModulesQuickJsVersion>(modules_quickjs_version_map);
|
||||
auto quickjs_bytecode =
|
||||
ctx.tx.wo<ccf::ModulesQuickJsBytecode>(modules_quickjs_bytecode_map);
|
||||
|
||||
quickjs_version->put(ccf::quickjs_version);
|
||||
quickjs_bytecode->clear();
|
||||
|
||||
modules->foreach([&](const auto& name, const auto& src) {
|
||||
auto module_val = ccf::js::load_app_module(
|
||||
jsctx,
|
||||
name.c_str(),
|
||||
&ctx.tx,
|
||||
modules_map,
|
||||
modules_quickjs_bytecode_map,
|
||||
modules_quickjs_version_map);
|
||||
|
||||
uint8_t* out_buf;
|
||||
size_t out_buf_len;
|
||||
int flags = JS_WRITE_OBJ_BYTECODE;
|
||||
out_buf = JS_WriteObject(jsctx, &out_buf_len, module_val.val, flags);
|
||||
if (!out_buf)
|
||||
{
|
||||
throw std::runtime_error(fmt::format(
|
||||
"Unable to serialize bytecode for JS module '{}'", name));
|
||||
}
|
||||
|
||||
quickjs_bytecode->put(name, {out_buf, out_buf + out_buf_len});
|
||||
js_free(jsctx, out_buf);
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
ccf::endpoints::EndpointDefinitionPtr find_endpoint(
|
||||
kv::Tx& tx, ccf::RpcContext& rpc_ctx) override
|
||||
{
|
||||
// Look up the endpoint definition
|
||||
// First in the user-defined endpoints, and then fall-back to built-ins
|
||||
const auto method = rpc_ctx.get_method();
|
||||
const auto verb = rpc_ctx.get_request_verb();
|
||||
|
||||
auto endpoints = tx.ro<ccf::endpoints::EndpointsMap>(metadata_map);
|
||||
const auto key = ccf::endpoints::EndpointKey{method, verb};
|
||||
|
||||
// Look for a direct match of the given path
|
||||
const auto it = endpoints->get(key);
|
||||
if (it.has_value())
|
||||
{
|
||||
auto endpoint_def = std::make_shared<CustomJSEndpoint>();
|
||||
endpoint_def->dispatch = key;
|
||||
endpoint_def->properties = it.value();
|
||||
endpoint_def->full_uri_path =
|
||||
fmt::format("/{}{}", method_prefix, endpoint_def->dispatch.uri_path);
|
||||
ccf::instantiate_authn_policies(*endpoint_def);
|
||||
return endpoint_def;
|
||||
}
|
||||
|
||||
// If that doesn't exist, look through _all_ the endpoints to find
|
||||
// templated matches. If there is one, that's a match. More is an error,
|
||||
// none means delegate to the base class.
|
||||
{
|
||||
std::vector<ccf::endpoints::EndpointDefinitionPtr> matches;
|
||||
|
||||
endpoints->foreach_key([this, &endpoints, &matches, &key, &rpc_ctx](
|
||||
const auto& other_key) {
|
||||
if (key.verb == other_key.verb)
|
||||
{
|
||||
const auto opt_spec =
|
||||
ccf::endpoints::PathTemplateSpec::parse(other_key.uri_path);
|
||||
if (opt_spec.has_value())
|
||||
{
|
||||
const auto& template_spec = opt_spec.value();
|
||||
// This endpoint has templates in its path, and the correct verb
|
||||
// - now check if template matches the current request's path
|
||||
std::smatch match;
|
||||
if (std::regex_match(
|
||||
key.uri_path, match, template_spec.template_regex))
|
||||
{
|
||||
if (matches.empty())
|
||||
{
|
||||
auto ctx_impl = static_cast<ccf::RpcContextImpl*>(&rpc_ctx);
|
||||
if (ctx_impl == nullptr)
|
||||
{
|
||||
throw std::logic_error("Unexpected type of RpcContext");
|
||||
}
|
||||
// Populate the request_path_params while we have the match,
|
||||
// though this will be discarded on error if we later find
|
||||
// multiple matches
|
||||
auto& path_params = ctx_impl->path_params;
|
||||
for (size_t i = 0;
|
||||
i < template_spec.template_component_names.size();
|
||||
++i)
|
||||
{
|
||||
const auto& template_name =
|
||||
template_spec.template_component_names[i];
|
||||
const auto& template_value = match[i + 1].str();
|
||||
path_params[template_name] = template_value;
|
||||
}
|
||||
}
|
||||
|
||||
auto endpoint = std::make_shared<ccf::js::JSDynamicEndpoint>();
|
||||
endpoint->dispatch = other_key;
|
||||
endpoint->full_uri_path = fmt::format(
|
||||
"/{}{}", method_prefix, endpoint->dispatch.uri_path);
|
||||
endpoint->properties = endpoints->get(other_key).value();
|
||||
ccf::instantiate_authn_policies(*endpoint);
|
||||
matches.push_back(endpoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
if (matches.size() > 1)
|
||||
{
|
||||
report_ambiguous_templated_path(key.uri_path, matches);
|
||||
}
|
||||
else if (matches.size() == 1)
|
||||
{
|
||||
return matches[0];
|
||||
}
|
||||
}
|
||||
|
||||
return ccf::endpoints::EndpointRegistry::find_endpoint(tx, rpc_ctx);
|
||||
}
|
||||
|
||||
using PreExecutionHook = std::function<void(ccf::js::core::Context&)>;
|
||||
|
||||
void do_execute_request(
|
||||
const CustomJSEndpoint* endpoint,
|
||||
ccf::endpoints::EndpointContext& endpoint_ctx,
|
||||
const std::optional<PreExecutionHook>& pre_exec_hook = std::nullopt)
|
||||
{
|
||||
// This KV Value should be updated by any governance actions which modify
|
||||
// the JS app (including _any_ of its contained modules). We then use the
|
||||
// version where it was last modified as a safe approximation of when an
|
||||
// interpreter is unsafe to use. If this value is written to, the
|
||||
// version_of_previous_write will advance, and all cached interpreters
|
||||
// will be flushed.
|
||||
const auto interpreter_flush =
|
||||
endpoint_ctx.tx.ro<ccf::InterpreterFlush>(interpreter_flush_map);
|
||||
const auto flush_marker =
|
||||
interpreter_flush->get_version_of_previous_write().value_or(0);
|
||||
|
||||
const auto rw_access =
|
||||
endpoint->properties.mode == ccf::endpoints::Mode::ReadWrite ?
|
||||
ccf::js::TxAccess::APP_RW :
|
||||
ccf::js::TxAccess::APP_RO;
|
||||
std::optional<ccf::endpoints::InterpreterReusePolicy> reuse_policy =
|
||||
endpoint->properties.interpreter_reuse;
|
||||
std::shared_ptr<ccf::js::core::Context> interpreter =
|
||||
interpreter_cache->get_interpreter(
|
||||
rw_access, reuse_policy, flush_marker);
|
||||
if (interpreter == nullptr)
|
||||
{
|
||||
throw std::logic_error("Cache failed to produce interpreter");
|
||||
}
|
||||
ccf::js::core::Context& ctx = *interpreter;
|
||||
|
||||
// Prevent any other thread modifying this interpreter, until this
|
||||
// function completes. We could create interpreters per-thread, but then
|
||||
// we would get no cross-thread caching benefit (and would need to either
|
||||
// enforce, or share, caps across per-thread caches). We choose
|
||||
// instead to allow interpreters to be maximally reused, even across
|
||||
// threads, at the cost of locking (and potentially stalling another
|
||||
// thread's request execution) here.
|
||||
std::lock_guard<ccf::pal::Mutex> guard(ctx.lock);
|
||||
// Update the top of the stack for the current thread, used by the stack
|
||||
// guard Note this is only active outside SGX
|
||||
JS_UpdateStackTop(ctx.runtime());
|
||||
// Make the heap and stack limits safe while we init the runtime
|
||||
ctx.runtime().reset_runtime_options();
|
||||
|
||||
JS_SetModuleLoaderFunc(
|
||||
ctx.runtime(),
|
||||
nullptr,
|
||||
ccf::js::js_app_module_loader,
|
||||
&endpoint_ctx.tx);
|
||||
|
||||
// Extensions with a dependency on this endpoint context (invocation),
|
||||
// which must be removed after execution.
|
||||
ccf::js::extensions::Extensions local_extensions;
|
||||
|
||||
// ccf.kv.*
|
||||
local_extensions.emplace_back(
|
||||
std::make_shared<ccf::js::extensions::KvExtension>(&endpoint_ctx.tx));
|
||||
|
||||
// ccf.rpc.*
|
||||
local_extensions.emplace_back(
|
||||
std::make_shared<ccf::js::extensions::RpcExtension>(
|
||||
endpoint_ctx.rpc_ctx.get()));
|
||||
|
||||
auto request_extension =
|
||||
std::make_shared<ccf::js::extensions::RequestExtension>(
|
||||
endpoint_ctx.rpc_ctx.get());
|
||||
local_extensions.push_back(request_extension);
|
||||
|
||||
for (auto extension : local_extensions)
|
||||
{
|
||||
ctx.add_extension(extension);
|
||||
}
|
||||
|
||||
if (pre_exec_hook.has_value())
|
||||
{
|
||||
pre_exec_hook.value()(ctx);
|
||||
}
|
||||
|
||||
ccf::js::core::JSWrappedValue export_func;
|
||||
try
|
||||
{
|
||||
const auto& props = endpoint->properties;
|
||||
auto module_val = ccf::js::load_app_module(
|
||||
ctx,
|
||||
props.js_module.c_str(),
|
||||
&endpoint_ctx.tx,
|
||||
modules_map,
|
||||
modules_quickjs_bytecode_map,
|
||||
modules_quickjs_version_map);
|
||||
export_func = ctx.get_exported_function(
|
||||
module_val, props.js_function, props.js_module);
|
||||
}
|
||||
catch (const std::exception& exc)
|
||||
{
|
||||
endpoint_ctx.rpc_ctx->set_error(
|
||||
HTTP_STATUS_INTERNAL_SERVER_ERROR,
|
||||
ccf::errors::InternalError,
|
||||
exc.what());
|
||||
return;
|
||||
}
|
||||
|
||||
// Call exported function;
|
||||
auto request = request_extension->create_request_obj(
|
||||
ctx, endpoint->full_uri_path, endpoint_ctx, this);
|
||||
|
||||
auto val = ctx.call_with_rt_options(
|
||||
export_func,
|
||||
{request},
|
||||
&endpoint_ctx.tx,
|
||||
ccf::js::core::RuntimeLimitsPolicy::NONE);
|
||||
|
||||
for (auto extension : local_extensions)
|
||||
{
|
||||
ctx.remove_extension(extension);
|
||||
}
|
||||
|
||||
const auto& rt = ctx.runtime();
|
||||
|
||||
if (val.is_exception())
|
||||
{
|
||||
bool time_out = ctx.interrupt_data.request_timed_out;
|
||||
std::string error_msg = "Exception thrown while executing.";
|
||||
if (time_out)
|
||||
{
|
||||
error_msg = "Operation took too long to complete.";
|
||||
}
|
||||
|
||||
auto [reason, trace] = ctx.error_message();
|
||||
|
||||
if (rt.log_exception_details)
|
||||
{
|
||||
CCF_APP_FAIL("{}: {}", reason, trace.value_or("<no trace>"));
|
||||
}
|
||||
|
||||
if (rt.return_exception_details)
|
||||
{
|
||||
std::vector<nlohmann::json> details = {ccf::ODataJSExceptionDetails{
|
||||
ccf::errors::JSException, reason, trace}};
|
||||
endpoint_ctx.rpc_ctx->set_error(
|
||||
HTTP_STATUS_INTERNAL_SERVER_ERROR,
|
||||
ccf::errors::InternalError,
|
||||
std::move(error_msg),
|
||||
std::move(details));
|
||||
}
|
||||
else
|
||||
{
|
||||
endpoint_ctx.rpc_ctx->set_error(
|
||||
HTTP_STATUS_INTERNAL_SERVER_ERROR,
|
||||
ccf::errors::InternalError,
|
||||
std::move(error_msg));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle return value: {body, headers, statusCode}
|
||||
if (!val.is_obj())
|
||||
{
|
||||
endpoint_ctx.rpc_ctx->set_error(
|
||||
HTTP_STATUS_INTERNAL_SERVER_ERROR,
|
||||
ccf::errors::InternalError,
|
||||
"Invalid endpoint function return value (not an object).");
|
||||
return;
|
||||
}
|
||||
|
||||
// Response body (also sets a default response content-type header)
|
||||
{
|
||||
auto response_body_js = val["body"];
|
||||
if (!response_body_js.is_undefined())
|
||||
{
|
||||
std::vector<uint8_t> response_body;
|
||||
size_t buf_size;
|
||||
size_t buf_offset;
|
||||
auto typed_array_buffer = ctx.get_typed_array_buffer(
|
||||
response_body_js, &buf_offset, &buf_size, nullptr);
|
||||
uint8_t* array_buffer;
|
||||
if (!typed_array_buffer.is_exception())
|
||||
{
|
||||
size_t buf_size_total;
|
||||
array_buffer =
|
||||
JS_GetArrayBuffer(ctx, &buf_size_total, typed_array_buffer.val);
|
||||
array_buffer += buf_offset;
|
||||
}
|
||||
else
|
||||
{
|
||||
array_buffer =
|
||||
JS_GetArrayBuffer(ctx, &buf_size, response_body_js.val);
|
||||
}
|
||||
if (array_buffer)
|
||||
{
|
||||
endpoint_ctx.rpc_ctx->set_response_header(
|
||||
http::headers::CONTENT_TYPE,
|
||||
http::headervalues::contenttype::OCTET_STREAM);
|
||||
response_body =
|
||||
std::vector<uint8_t>(array_buffer, array_buffer + buf_size);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::optional<std::string> str;
|
||||
if (response_body_js.is_str())
|
||||
{
|
||||
endpoint_ctx.rpc_ctx->set_response_header(
|
||||
http::headers::CONTENT_TYPE,
|
||||
http::headervalues::contenttype::TEXT);
|
||||
str = ctx.to_str(response_body_js);
|
||||
}
|
||||
else
|
||||
{
|
||||
endpoint_ctx.rpc_ctx->set_response_header(
|
||||
http::headers::CONTENT_TYPE,
|
||||
http::headervalues::contenttype::JSON);
|
||||
auto rval = ctx.json_stringify(response_body_js);
|
||||
if (rval.is_exception())
|
||||
{
|
||||
auto [reason, trace] = ctx.error_message();
|
||||
|
||||
if (rt.log_exception_details)
|
||||
{
|
||||
CCF_APP_FAIL(
|
||||
"Failed to convert return value to JSON:{} {}",
|
||||
reason,
|
||||
trace.value_or("<no trace>"));
|
||||
}
|
||||
|
||||
if (rt.return_exception_details)
|
||||
{
|
||||
std::vector<nlohmann::json> details = {
|
||||
ccf::ODataJSExceptionDetails{
|
||||
ccf::errors::JSException, reason, trace}};
|
||||
endpoint_ctx.rpc_ctx->set_error(
|
||||
HTTP_STATUS_INTERNAL_SERVER_ERROR,
|
||||
ccf::errors::InternalError,
|
||||
"Invalid endpoint function return value (error during JSON "
|
||||
"conversion of body)",
|
||||
std::move(details));
|
||||
}
|
||||
else
|
||||
{
|
||||
endpoint_ctx.rpc_ctx->set_error(
|
||||
HTTP_STATUS_INTERNAL_SERVER_ERROR,
|
||||
ccf::errors::InternalError,
|
||||
"Invalid endpoint function return value (error during JSON "
|
||||
"conversion of body).");
|
||||
}
|
||||
return;
|
||||
}
|
||||
str = ctx.to_str(rval);
|
||||
}
|
||||
|
||||
if (!str)
|
||||
{
|
||||
auto [reason, trace] = ctx.error_message();
|
||||
|
||||
if (rt.log_exception_details)
|
||||
{
|
||||
CCF_APP_FAIL(
|
||||
"Failed to convert return value to JSON:{} {}",
|
||||
reason,
|
||||
trace.value_or("<no trace>"));
|
||||
}
|
||||
|
||||
if (rt.return_exception_details)
|
||||
{
|
||||
std::vector<nlohmann::json> details = {
|
||||
ccf::ODataJSExceptionDetails{
|
||||
ccf::errors::JSException, reason, trace}};
|
||||
endpoint_ctx.rpc_ctx->set_error(
|
||||
HTTP_STATUS_INTERNAL_SERVER_ERROR,
|
||||
ccf::errors::InternalError,
|
||||
"Invalid endpoint function return value (error during string "
|
||||
"conversion of body).",
|
||||
std::move(details));
|
||||
}
|
||||
else
|
||||
{
|
||||
endpoint_ctx.rpc_ctx->set_error(
|
||||
HTTP_STATUS_INTERNAL_SERVER_ERROR,
|
||||
ccf::errors::InternalError,
|
||||
"Invalid endpoint function return value (error during string "
|
||||
"conversion of body).");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
response_body = std::vector<uint8_t>(str->begin(), str->end());
|
||||
}
|
||||
endpoint_ctx.rpc_ctx->set_response_body(std::move(response_body));
|
||||
}
|
||||
}
|
||||
|
||||
// Response headers
|
||||
{
|
||||
auto response_headers_js = val["headers"];
|
||||
if (response_headers_js.is_obj())
|
||||
{
|
||||
ccf::js::core::JSWrappedPropertyEnum prop_enum(
|
||||
ctx, response_headers_js);
|
||||
for (size_t i = 0; i < prop_enum.size(); i++)
|
||||
{
|
||||
auto prop_name = ctx.to_str(prop_enum[i]);
|
||||
if (!prop_name)
|
||||
{
|
||||
endpoint_ctx.rpc_ctx->set_error(
|
||||
HTTP_STATUS_INTERNAL_SERVER_ERROR,
|
||||
ccf::errors::InternalError,
|
||||
"Invalid endpoint function return value (header type).");
|
||||
return;
|
||||
}
|
||||
auto prop_val = response_headers_js[*prop_name];
|
||||
auto prop_val_str = ctx.to_str(prop_val);
|
||||
if (!prop_val_str)
|
||||
{
|
||||
endpoint_ctx.rpc_ctx->set_error(
|
||||
HTTP_STATUS_INTERNAL_SERVER_ERROR,
|
||||
ccf::errors::InternalError,
|
||||
"Invalid endpoint function return value (header value type).");
|
||||
return;
|
||||
}
|
||||
endpoint_ctx.rpc_ctx->set_response_header(
|
||||
*prop_name, *prop_val_str);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Response status code
|
||||
int response_status_code = HTTP_STATUS_OK;
|
||||
{
|
||||
auto status_code_js = val["statusCode"];
|
||||
if (!status_code_js.is_undefined() && !JS_IsNull(status_code_js.val))
|
||||
{
|
||||
if (JS_VALUE_GET_TAG(status_code_js.val) != JS_TAG_INT)
|
||||
{
|
||||
endpoint_ctx.rpc_ctx->set_error(
|
||||
HTTP_STATUS_INTERNAL_SERVER_ERROR,
|
||||
ccf::errors::InternalError,
|
||||
"Invalid endpoint function return value (status code type).");
|
||||
return;
|
||||
}
|
||||
response_status_code = JS_VALUE_GET_INT(status_code_js.val);
|
||||
}
|
||||
endpoint_ctx.rpc_ctx->set_response_status(response_status_code);
|
||||
}
|
||||
}
|
||||
|
||||
void execute_request(
|
||||
const CustomJSEndpoint* endpoint,
|
||||
ccf::endpoints::EndpointContext& endpoint_ctx)
|
||||
{
|
||||
do_execute_request(endpoint, endpoint_ctx);
|
||||
}
|
||||
|
||||
void execute_endpoint(
|
||||
ccf::endpoints::EndpointDefinitionPtr e,
|
||||
ccf::endpoints::EndpointContext& endpoint_ctx) override
|
||||
{
|
||||
// Handle endpoint execution
|
||||
auto endpoint = dynamic_cast<const CustomJSEndpoint*>(e.get());
|
||||
if (endpoint != nullptr)
|
||||
{
|
||||
execute_request(endpoint, endpoint_ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
ccf::endpoints::EndpointRegistry::execute_endpoint(e, endpoint_ctx);
|
||||
}
|
||||
|
||||
void execute_request_locally_committed(
|
||||
const CustomJSEndpoint* endpoint,
|
||||
ccf::endpoints::CommandEndpointContext& endpoint_ctx,
|
||||
const ccf::TxID& tx_id)
|
||||
{
|
||||
ccf::endpoints::default_locally_committed_func(endpoint_ctx, tx_id);
|
||||
}
|
||||
|
||||
void execute_endpoint_locally_committed(
|
||||
ccf::endpoints::EndpointDefinitionPtr e,
|
||||
ccf::endpoints::CommandEndpointContext& endpoint_ctx,
|
||||
const ccf::TxID& tx_id) override
|
||||
{
|
||||
auto endpoint = dynamic_cast<const CustomJSEndpoint*>(e.get());
|
||||
if (endpoint != nullptr)
|
||||
{
|
||||
execute_request_locally_committed(endpoint, endpoint_ctx, tx_id);
|
||||
return;
|
||||
}
|
||||
|
||||
ccf::endpoints::EndpointRegistry::execute_endpoint_locally_committed(
|
||||
e, endpoint_ctx, tx_id);
|
||||
}
|
||||
};
|
||||
}
|
|
@ -10,6 +10,7 @@
|
|||
#include "ccf/http_consts.h"
|
||||
#include "ccf/http_responder.h"
|
||||
#include "ccf/json_handler.h"
|
||||
#include "ccf/node/rpc_context_impl.h"
|
||||
#include "ccf/service/tables/nodes.h"
|
||||
#include "executor_auth_policy.h"
|
||||
#include "executor_code_id.h"
|
||||
|
@ -22,7 +23,6 @@
|
|||
#include "misc.pb.h"
|
||||
#include "node/endpoint_context_impl.h"
|
||||
#include "node/rpc/network_identity_subsystem.h"
|
||||
#include "node/rpc/rpc_context_impl.h"
|
||||
|
||||
#define FMT_HEADER_ONLY
|
||||
#include <fmt/format.h>
|
||||
|
|
|
@ -1,31 +1,31 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the Apache 2.0 License.
|
||||
#include "apps/js_generic/named_auth_policies.h"
|
||||
#include "apps/js_generic/request_extension.h"
|
||||
#include "ccf/app_interface.h"
|
||||
#include "ccf/crypto/key_wrap.h"
|
||||
#include "ccf/crypto/rsa_key_pair.h"
|
||||
#include "ccf/endpoints/authentication/all_of_auth.h"
|
||||
#include "ccf/historical_queries_adapter.h"
|
||||
#include "ccf/js/core/context.h"
|
||||
#include "ccf/js/core/wrapped_property_enum.h"
|
||||
#include "ccf/js/extensions/ccf/consensus.h"
|
||||
#include "ccf/js/extensions/ccf/converters.h"
|
||||
#include "ccf/js/extensions/ccf/crypto.h"
|
||||
#include "ccf/js/extensions/ccf/historical.h"
|
||||
#include "ccf/js/extensions/ccf/host.h"
|
||||
#include "ccf/js/extensions/ccf/kv.h"
|
||||
#include "ccf/js/extensions/ccf/request.h"
|
||||
#include "ccf/js/extensions/ccf/rpc.h"
|
||||
#include "ccf/js/extensions/console.h"
|
||||
#include "ccf/js/extensions/math/random.h"
|
||||
#include "ccf/js/modules.h"
|
||||
#include "ccf/js/named_auth_policies.h"
|
||||
#include "ccf/node/host_processes_interface.h"
|
||||
#include "ccf/node/rpc_context_impl.h"
|
||||
#include "ccf/service/tables/jsengine.h"
|
||||
#include "ccf/version.h"
|
||||
#include "enclave/enclave_time.h"
|
||||
#include "js/core/context.h"
|
||||
#include "js/core/wrapped_property_enum.h"
|
||||
#include "js/extensions/ccf/consensus.h"
|
||||
#include "js/extensions/ccf/converters.h"
|
||||
#include "js/extensions/ccf/crypto.h"
|
||||
#include "js/extensions/ccf/historical.h"
|
||||
#include "js/extensions/ccf/host.h"
|
||||
#include "js/extensions/ccf/kv.h"
|
||||
#include "js/extensions/ccf/rpc.h"
|
||||
#include "js/extensions/console.h"
|
||||
#include "js/extensions/math/random.h"
|
||||
#include "js/global_class_ids.h"
|
||||
#include "js/interpreter_cache_interface.h"
|
||||
#include "js/modules.h"
|
||||
#include "node/rpc/rpc_context_impl.h"
|
||||
#include "service/tables/endpoints.h"
|
||||
|
||||
#include <memory>
|
||||
|
@ -123,7 +123,8 @@ namespace ccfapp
|
|||
js::TxAccess::APP_RW :
|
||||
js::TxAccess::APP_RO;
|
||||
std::shared_ptr<js::core::Context> interpreter =
|
||||
interpreter_cache->get_interpreter(rw_access, *endpoint, flush_marker);
|
||||
interpreter_cache->get_interpreter(
|
||||
rw_access, endpoint->properties.interpreter_reuse, flush_marker);
|
||||
if (interpreter == nullptr)
|
||||
{
|
||||
throw std::logic_error("Cache failed to produce interpreter");
|
||||
|
@ -161,7 +162,8 @@ namespace ccfapp
|
|||
endpoint_ctx.rpc_ctx.get()));
|
||||
|
||||
auto request_extension =
|
||||
std::make_shared<RequestExtension>(endpoint_ctx.rpc_ctx.get());
|
||||
std::make_shared<ccf::js::extensions::RequestExtension>(
|
||||
endpoint_ctx.rpc_ctx.get());
|
||||
local_extensions.push_back(request_extension);
|
||||
|
||||
for (auto extension : local_extensions)
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#pragma once
|
||||
#include "ccf/app_interface.h"
|
||||
#include "ccf/ds/logger.h"
|
||||
#include "ccf/js/core/context.h"
|
||||
#include "ccf/node_context.h"
|
||||
#include "ccf/node_subsystem_interface.h"
|
||||
#include "ccf/pal/enclave.h"
|
||||
|
@ -13,7 +14,6 @@
|
|||
#include "indexing/enclave_lfs_access.h"
|
||||
#include "indexing/historical_transaction_fetcher.h"
|
||||
#include "interface.h"
|
||||
#include "js/core/context.h"
|
||||
#include "js/ffi_plugins.h"
|
||||
#include "js/interpreter_cache.h"
|
||||
#include "node/acme_challenge_frontend.h"
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
#include "ccf/endpoint_registry.h"
|
||||
|
||||
#include "ccf/common_auth_policies.h"
|
||||
#include "ccf/node/rpc_context_impl.h"
|
||||
#include "ccf/pal/locking.h"
|
||||
#include "http/http_parser.h"
|
||||
#include "node/rpc/rpc_context_impl.h"
|
||||
|
||||
namespace ccf::endpoints
|
||||
{
|
||||
|
|
|
@ -3,9 +3,9 @@
|
|||
#pragma once
|
||||
|
||||
#include "ccf/endpoint_context.h"
|
||||
#include "ccf/node/rpc_context_impl.h"
|
||||
#include "ccf/odata_error.h"
|
||||
#include "message.h"
|
||||
#include "node/rpc/rpc_context_impl.h"
|
||||
#include "node/rpc/rpc_exception.h"
|
||||
#include "stream.h"
|
||||
#include "types.h"
|
||||
|
|
|
@ -4,10 +4,10 @@
|
|||
|
||||
#include "ccf/actors.h"
|
||||
#include "ccf/http_responder.h"
|
||||
#include "ccf/node/rpc_context_impl.h"
|
||||
#include "ccf/odata_error.h"
|
||||
#include "ccf/rpc_context.h"
|
||||
#include "http_parser.h"
|
||||
#include "node/rpc/rpc_context_impl.h"
|
||||
|
||||
namespace http
|
||||
{
|
||||
|
|
|
@ -2,12 +2,12 @@
|
|||
// Licensed under the Apache 2.0 License.
|
||||
#pragma once
|
||||
|
||||
#include "js/core/context.h"
|
||||
#include "js/extensions/ccf/converters.h"
|
||||
#include "js/extensions/ccf/crypto.h"
|
||||
#include "js/extensions/ccf/kv.h"
|
||||
#include "js/extensions/console.h"
|
||||
#include "js/extensions/math/random.h"
|
||||
#include "ccf/js/core/context.h"
|
||||
#include "ccf/js/extensions/ccf/converters.h"
|
||||
#include "ccf/js/extensions/ccf/crypto.h"
|
||||
#include "ccf/js/extensions/ccf/kv.h"
|
||||
#include "ccf/js/extensions/console.h"
|
||||
#include "ccf/js/extensions/math/random.h"
|
||||
|
||||
namespace ccf::js
|
||||
{
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the Apache 2.0 License.
|
||||
|
||||
#include "js/core/context.h"
|
||||
#include "ccf/js/core/context.h"
|
||||
|
||||
#include "ccf/ds/hex.h"
|
||||
#include "ccf/js/core/runtime.h"
|
||||
#include "ccf/js/core/wrapped_value.h"
|
||||
#include "ccf/js/extensions/console.h"
|
||||
#include "ccf/js/tx_access.h"
|
||||
#include "ccf/pal/locking.h"
|
||||
#include "enclave/enclave_time.h"
|
||||
#include "js/core/runtime.h"
|
||||
#include "js/core/wrapped_value.h"
|
||||
#include "js/extensions/console.h"
|
||||
#include "js/ffi_plugins.h"
|
||||
#include "js/global_class_ids.h"
|
||||
#include "js/tx_access.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <quickjs/quickjs.h>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the Apache 2.0 License.
|
||||
|
||||
#include "js/core/runtime.h"
|
||||
#include "ccf/js/core/runtime.h"
|
||||
|
||||
#include "ccf/service/tables/jsengine.h"
|
||||
#include "ccf/tx.h"
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the Apache 2.0 License.
|
||||
|
||||
#include "js/core/wrapped_value.h"
|
||||
#include "ccf/js/core/wrapped_value.h"
|
||||
|
||||
#include "js/core/constants.h"
|
||||
#include "ccf/js/core/constants.h"
|
||||
|
||||
namespace ccf::js::core
|
||||
{
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the Apache 2.0 License.
|
||||
|
||||
#include "js/extensions/ccf/consensus.h"
|
||||
#include "ccf/js/extensions/ccf/consensus.h"
|
||||
|
||||
#include "ccf/base_endpoint_registry.h"
|
||||
#include "ccf/js/core/context.h"
|
||||
#include "js/checks.h"
|
||||
#include "js/core/context.h"
|
||||
|
||||
#include <quickjs/quickjs.h>
|
||||
|
||||
|
|
|
@ -4,12 +4,12 @@
|
|||
// NB: Despite the naming scheme used elsewhere, this populates functions
|
||||
// directly on the ccf object.
|
||||
|
||||
#include "js/extensions/ccf/converters.h"
|
||||
#include "ccf/js/extensions/ccf/converters.h"
|
||||
|
||||
#include "ccf/js/core/context.h"
|
||||
#include "ccf/js/modules.h"
|
||||
#include "ccf/version.h"
|
||||
#include "js/checks.h"
|
||||
#include "js/core/context.h"
|
||||
#include "js/modules.h"
|
||||
#include "node/rpc/jwt_management.h"
|
||||
|
||||
#include <quickjs/quickjs.h>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the Apache 2.0 License.
|
||||
|
||||
#include "js/extensions/ccf/crypto.h"
|
||||
#include "ccf/js/extensions/ccf/crypto.h"
|
||||
|
||||
#include "ccf/crypto/ecdsa.h"
|
||||
#include "ccf/crypto/eddsa_key_pair.h"
|
||||
|
@ -11,8 +11,8 @@
|
|||
#include "ccf/crypto/rsa_key_pair.h"
|
||||
#include "ccf/crypto/sha256.h"
|
||||
#include "ccf/crypto/verifier.h"
|
||||
#include "ccf/js/core/context.h"
|
||||
#include "js/checks.h"
|
||||
#include "js/core/context.h"
|
||||
#include "tls/ca.h"
|
||||
|
||||
namespace ccf::js::extensions
|
||||
|
|
|
@ -4,11 +4,11 @@
|
|||
// NB: Despite the naming scheme used elsewhere, this populates functions
|
||||
// directly on the ccf object.
|
||||
|
||||
#include "js/extensions/ccf/gov_effects.h"
|
||||
#include "ccf/js/extensions/ccf/gov_effects.h"
|
||||
|
||||
#include "ccf/js/core/context.h"
|
||||
#include "ccf/js/modules.h"
|
||||
#include "ccf/version.h"
|
||||
#include "js/core/context.h"
|
||||
#include "js/modules.h"
|
||||
#include "node/rpc/jwt_management.h"
|
||||
|
||||
#include <quickjs/quickjs.h>
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the Apache 2.0 License.
|
||||
|
||||
#include "js/extensions/ccf/historical.h"
|
||||
#include "ccf/js/extensions/ccf/historical.h"
|
||||
|
||||
#include "ccf/ds/hex.h"
|
||||
#include "ccf/historical_queries_interface.h"
|
||||
#include "ccf/js/core/context.h"
|
||||
#include "js/checks.h"
|
||||
#include "js/core/context.h"
|
||||
#include "js/extensions/ccf/kv_helpers.h"
|
||||
#include "kv/untyped_map.h"
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the Apache 2.0 License.
|
||||
|
||||
#include "js/extensions/ccf/host.h"
|
||||
#include "ccf/js/extensions/ccf/host.h"
|
||||
|
||||
#include "js/core/context.h"
|
||||
#include "ccf/js/core/context.h"
|
||||
|
||||
#include <quickjs/quickjs.h>
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the Apache 2.0 License.
|
||||
|
||||
#include "js/extensions/ccf/kv.h"
|
||||
#include "ccf/js/extensions/ccf/kv.h"
|
||||
|
||||
#include "ccf/js/core/context.h"
|
||||
#include "js/checks.h"
|
||||
#include "js/core/context.h"
|
||||
#include "js/extensions/ccf/kv_helpers.h"
|
||||
#include "js/global_class_ids.h"
|
||||
#include "js/map_access_permissions.h"
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
#include "js/extensions/ccf/network.h"
|
||||
|
||||
#include "js/core/context.h"
|
||||
#include "ccf/js/core/context.h"
|
||||
#include "node/network_state.h"
|
||||
|
||||
#include <quickjs/quickjs.h>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
// Licensed under the Apache 2.0 License.
|
||||
#pragma once
|
||||
|
||||
#include "js/extensions/extension_interface.h"
|
||||
#include "ccf/js/extensions/extension_interface.h"
|
||||
#include "node/network_state.h"
|
||||
|
||||
namespace ccf::js::extensions
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
#include "js/extensions/ccf/node.h"
|
||||
|
||||
#include "js/core/context.h"
|
||||
#include "ccf/js/core/context.h"
|
||||
#include "node/rpc/gov_logging.h"
|
||||
|
||||
#include <quickjs/quickjs.h>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
// Licensed under the Apache 2.0 License.
|
||||
#pragma once
|
||||
|
||||
#include "js/extensions/extension_interface.h"
|
||||
#include "ccf/js/extensions/extension_interface.h"
|
||||
#include "node/rpc/gov_effects_interface.h"
|
||||
|
||||
namespace ccf::js::extensions
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the Apache 2.0 License.
|
||||
|
||||
#include "apps/js_generic/request_extension.h"
|
||||
#include "ccf/js/extensions/ccf/request.h"
|
||||
|
||||
#include "apps/js_generic/named_auth_policies.h"
|
||||
#include "ccf/endpoints/authentication/all_of_auth.h"
|
||||
#include "ccf/endpoints/authentication/cert_auth.h"
|
||||
#include "ccf/endpoints/authentication/cose_auth.h"
|
||||
#include "ccf/endpoints/authentication/empty_auth.h"
|
||||
#include "ccf/endpoints/authentication/jwt_auth.h"
|
||||
#include "js/core/context.h"
|
||||
#include "ccf/js/core/context.h"
|
||||
#include "ccf/js/named_auth_policies.h"
|
||||
|
||||
#include <quickjs/quickjs.h>
|
||||
|
||||
namespace ccfapp
|
||||
namespace ccf::js::extensions
|
||||
{
|
||||
namespace
|
||||
{
|
|
@ -1,10 +1,10 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the Apache 2.0 License.
|
||||
|
||||
#include "js/extensions/ccf/rpc.h"
|
||||
#include "ccf/js/extensions/ccf/rpc.h"
|
||||
|
||||
#include "ccf/js/core/context.h"
|
||||
#include "ccf/rpc_context.h"
|
||||
#include "js/core/context.h"
|
||||
|
||||
#include <quickjs/quickjs.h>
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the Apache 2.0 License.
|
||||
|
||||
#include "js/extensions/console.h"
|
||||
#include "ccf/js/extensions/console.h"
|
||||
|
||||
#include "js/core/context.h"
|
||||
#include "ccf/js/core/context.h"
|
||||
#include "node/rpc/gov_logging.h"
|
||||
|
||||
#include <quickjs/quickjs.h>
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the Apache 2.0 License.
|
||||
|
||||
#include "js/extensions/math/random.h"
|
||||
#include "ccf/js/extensions/math/random.h"
|
||||
|
||||
#include "ccf/crypto/entropy.h"
|
||||
#include "js/core/context.h"
|
||||
#include "ccf/js/core/context.h"
|
||||
|
||||
#include <quickjs/quickjs.h>
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
#include "js/global_class_ids.h"
|
||||
|
||||
#include "js/core/context.h"
|
||||
#include "ccf/js/core/context.h"
|
||||
|
||||
namespace ccf::js
|
||||
{
|
||||
|
|
|
@ -33,7 +33,8 @@ namespace ccf::js
|
|||
|
||||
std::shared_ptr<js::core::Context> get_interpreter(
|
||||
js::TxAccess access,
|
||||
const JSDynamicEndpoint& endpoint,
|
||||
const std::optional<ccf::endpoints::InterpreterReusePolicy>&
|
||||
interpreter_reuse,
|
||||
size_t freshness_marker) override
|
||||
{
|
||||
if (access != js::TxAccess::APP_RW && access != js::TxAccess::APP_RO)
|
||||
|
@ -55,13 +56,13 @@ namespace ccf::js
|
|||
cache_build_marker = freshness_marker;
|
||||
}
|
||||
|
||||
if (endpoint.properties.interpreter_reuse.has_value())
|
||||
if (interpreter_reuse.has_value())
|
||||
{
|
||||
switch (endpoint.properties.interpreter_reuse->kind)
|
||||
switch (interpreter_reuse->kind)
|
||||
{
|
||||
case ccf::endpoints::InterpreterReusePolicy::KeyBased:
|
||||
{
|
||||
auto key = endpoint.properties.interpreter_reuse->key;
|
||||
auto key = interpreter_reuse->key;
|
||||
if (access == js::TxAccess::APP_RW)
|
||||
{
|
||||
key += " (rw)";
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
#pragma once
|
||||
|
||||
#include "ccf/endpoint.h"
|
||||
#include "ccf/js/tx_access.h"
|
||||
#include "ccf/node_subsystem_interface.h"
|
||||
#include "js/tx_access.h"
|
||||
|
||||
namespace ccf::js
|
||||
{
|
||||
|
@ -38,7 +38,8 @@ namespace ccf::js
|
|||
// execution, where some global initialisation may already be done.
|
||||
virtual std::shared_ptr<js::core::Context> get_interpreter(
|
||||
js::TxAccess access,
|
||||
const JSDynamicEndpoint& endpoint,
|
||||
const std::optional<ccf::endpoints::InterpreterReusePolicy>&
|
||||
interpreter_reuse,
|
||||
size_t freshness_marker) = 0;
|
||||
|
||||
// Cap the total number of interpreters which will be retained. The
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
// Licensed under the Apache 2.0 License.
|
||||
#pragma once
|
||||
|
||||
#include "js/tx_access.h"
|
||||
#include "ccf/js/tx_access.h"
|
||||
#include "kv/kv_types.h"
|
||||
|
||||
namespace ccf::js
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
// Licensed under the Apache 2.0 License.
|
||||
|
||||
#include "ccf/ds/hex.h"
|
||||
#include "ccf/js/core/context.h"
|
||||
#include "ccf/js_openenclave_plugin.h"
|
||||
#include "ccf/js_plugin.h"
|
||||
#include "ccf/version.h"
|
||||
#include "js/checks.h"
|
||||
#include "js/core/context.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <openenclave/attestation/custom_claims.h>
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the Apache 2.0 License.
|
||||
|
||||
#include "ccf/js/core/context.h"
|
||||
#include "ccf/js_plugin.h"
|
||||
#include "ccf/js_snp_attestation_plugin.h"
|
||||
#include "ccf/pal/attestation.h"
|
||||
#include "ccf/version.h"
|
||||
#include "js/checks.h"
|
||||
#include "js/core/context.h"
|
||||
#include "node/uvm_endorsements.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
#pragma once
|
||||
|
||||
#include "ccf/base_endpoint_registry.h"
|
||||
#include "ccf/js/extensions/ccf/gov_effects.h"
|
||||
#include "js/common_context.h"
|
||||
#include "js/extensions/ccf/gov_effects.h"
|
||||
#include "js/extensions/ccf/network.h"
|
||||
#include "js/extensions/ccf/node.h"
|
||||
#include "node/gov/api_version.h"
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include "ccf/crypto/symmetric_key.h"
|
||||
#include "ccf/crypto/verifier.h"
|
||||
#include "ccf/ds/logger.h"
|
||||
#include "ccf/js/core/context.h"
|
||||
#include "ccf/pal/attestation.h"
|
||||
#include "ccf/pal/locking.h"
|
||||
#include "ccf/pal/platform.h"
|
||||
|
@ -25,7 +26,6 @@
|
|||
#include "history.h"
|
||||
#include "http/http_parser.h"
|
||||
#include "indexing/indexer.h"
|
||||
#include "js/core/context.h"
|
||||
#include "js/global_class_ids.h"
|
||||
#include "network_state.h"
|
||||
#include "node/hooks.h"
|
||||
|
|
|
@ -12,13 +12,6 @@ namespace ccf
|
|||
return ClaimsDigest();
|
||||
}
|
||||
|
||||
static ClaimsDigest empty_claims()
|
||||
{
|
||||
ClaimsDigest cd;
|
||||
cd.set(ClaimsDigest::Digest::Representation());
|
||||
return cd;
|
||||
}
|
||||
|
||||
static crypto::Sha256Hash entry_leaf(
|
||||
const std::vector<uint8_t>& write_set,
|
||||
const std::optional<crypto::Sha256Hash>& commit_evidence_digest,
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include "ccf/http_status.h"
|
||||
#include "ccf/node_context.h"
|
||||
#include "ccf/pal/locking.h"
|
||||
#include "ccf/research/grpc_status.h"
|
||||
#include "ccf/service/node_info_network.h"
|
||||
#include "ccf/service/signed_req.h"
|
||||
#include "ccf/service/tables/jwt.h"
|
||||
|
@ -13,7 +14,6 @@
|
|||
#include "ccf/service/tables/service.h"
|
||||
#include "common/configuration.h"
|
||||
#include "enclave/rpc_handler.h"
|
||||
#include "endpoints/grpc/grpc_status.h"
|
||||
#include "forwarder.h"
|
||||
#include "http/http_jwt.h"
|
||||
#include "kv/compacted_version_conflict.h"
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include "ccf/common_auth_policies.h"
|
||||
#include "ccf/common_endpoint_registry.h"
|
||||
#include "ccf/http_query.h"
|
||||
#include "ccf/js/core/context.h"
|
||||
#include "ccf/json_handler.h"
|
||||
#include "ccf/node/quote.h"
|
||||
#include "ccf/odata_error.h"
|
||||
|
@ -16,7 +17,6 @@
|
|||
#include "ds/std_formatters.h"
|
||||
#include "enclave/reconfiguration_type.h"
|
||||
#include "frontend.h"
|
||||
#include "js/core/context.h"
|
||||
#include "node/network_state.h"
|
||||
#include "node/rpc/jwt_management.h"
|
||||
#include "node/rpc/no_create_tx_claims_digest.cpp"
|
||||
|
|
|
@ -9,33 +9,4 @@ namespace ccf
|
|||
{
|
||||
using DynamicEndpoints =
|
||||
ccf::ServiceMap<endpoints::EndpointKey, endpoints::EndpointProperties>;
|
||||
}
|
||||
|
||||
namespace kv::serialisers
|
||||
{
|
||||
template <>
|
||||
struct BlitSerialiser<ccf::endpoints::EndpointKey>
|
||||
{
|
||||
static SerialisedEntry to_serialised(
|
||||
const ccf::endpoints::EndpointKey& endpoint_key)
|
||||
{
|
||||
auto str =
|
||||
fmt::format("{} {}", endpoint_key.verb.c_str(), endpoint_key.uri_path);
|
||||
return SerialisedEntry(str.begin(), str.end());
|
||||
}
|
||||
|
||||
static ccf::endpoints::EndpointKey from_serialised(
|
||||
const SerialisedEntry& data)
|
||||
{
|
||||
std::string str{data.begin(), data.end()};
|
||||
auto i = str.find(' ');
|
||||
if (i == std::string::npos)
|
||||
{
|
||||
throw std::logic_error("invalid encoding of endpoint key");
|
||||
}
|
||||
auto verb = str.substr(0, i);
|
||||
auto uri_path = str.substr(i + 1);
|
||||
return {uri_path, verb};
|
||||
}
|
||||
};
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# Licensed under the Apache 2.0 License.
|
||||
import infra.network
|
||||
import infra.e2e_args
|
||||
import infra.checker
|
||||
import infra.jwt_issuer
|
||||
import infra.proc
|
||||
import http
|
||||
from infra.runner import ConcurrentRunner
|
||||
|
||||
|
||||
TESTJS = """
|
||||
export function content(request) {
|
||||
return {
|
||||
statusCode: 200,
|
||||
body: {
|
||||
payload: "Test content",
|
||||
},
|
||||
};
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
def test_custom_endpoints(network, args):
|
||||
primary, _ = network.find_primary()
|
||||
|
||||
# Make user0 admin, so it can install custom endpoints
|
||||
user = network.users[0]
|
||||
network.consortium.set_user_data(
|
||||
primary, user.service_id, user_data={"isAdmin": True}
|
||||
)
|
||||
|
||||
content_endpoint_def = {
|
||||
"get": {
|
||||
"js_module": "test.js",
|
||||
"js_function": "content",
|
||||
"forwarding_required": "never",
|
||||
"redirection_strategy": "none",
|
||||
"authn_policies": ["no_auth"],
|
||||
"mode": "readonly",
|
||||
"openapi": {},
|
||||
}
|
||||
}
|
||||
|
||||
bundle_with_content = {
|
||||
"metadata": {"endpoints": {"/content": content_endpoint_def}},
|
||||
"modules": {"test.js": TESTJS},
|
||||
}
|
||||
|
||||
bundle_with_other_content = {
|
||||
"metadata": {"endpoints": {"/other_content": content_endpoint_def}},
|
||||
"modules": {"test.js": TESTJS},
|
||||
}
|
||||
|
||||
with primary.client(None, None, user.local_id) as c:
|
||||
r = c.put("/app/custom_endpoints", body={"bundle": bundle_with_content})
|
||||
assert r.status_code == http.HTTPStatus.NO_CONTENT.value, r.status_code
|
||||
|
||||
with primary.client() as c:
|
||||
r = c.get("/app/not_content")
|
||||
assert r.status_code == http.HTTPStatus.NOT_FOUND.value, r.status_code
|
||||
|
||||
r = c.get("/app/content")
|
||||
assert r.status_code == http.HTTPStatus.OK.value, r.status_code
|
||||
assert r.body.json()["payload"] == "Test content", r.body.json()
|
||||
|
||||
with primary.client(None, None, user.local_id) as c:
|
||||
r = c.put("/app/custom_endpoints", body={"bundle": bundle_with_other_content})
|
||||
assert r.status_code == http.HTTPStatus.NO_CONTENT.value, r.status_code
|
||||
|
||||
with primary.client() as c:
|
||||
r = c.get("/app/other_content")
|
||||
assert r.status_code == http.HTTPStatus.OK.value, r.status_code
|
||||
assert r.body.json()["payload"] == "Test content", r.body.json()
|
||||
|
||||
r = c.get("/app/content")
|
||||
assert r.status_code == http.HTTPStatus.NOT_FOUND.value, r.status_code
|
||||
|
||||
return network
|
||||
|
||||
|
||||
def run(args):
|
||||
with infra.network.network(
|
||||
args.nodes,
|
||||
args.binary_dir,
|
||||
args.debug_nodes,
|
||||
args.perf_nodes,
|
||||
pdb=args.pdb,
|
||||
) as network:
|
||||
network.start_and_open(args)
|
||||
|
||||
test_custom_endpoints(network, args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
cr = ConcurrentRunner()
|
||||
|
||||
cr.add(
|
||||
"basic",
|
||||
run,
|
||||
package="samples/apps/basic/libbasic",
|
||||
js_app_bundle=None,
|
||||
nodes=infra.e2e_args.min_nodes(cr.args, f=0),
|
||||
initial_user_count=2,
|
||||
initial_member_count=1,
|
||||
)
|
||||
|
||||
cr.run()
|
Загрузка…
Ссылка в новой задаче