This commit is contained in:
Amaury Chamayou 2024-05-24 16:59:22 +01:00 коммит произвёл GitHub
Родитель bdac0c49c2
Коммит 4a201a6f61
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
68 изменённых файлов: 1226 добавлений и 158 удалений

Просмотреть файл

@ -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(

39
include/ccf/bundle.h Normal file
Просмотреть файл

@ -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};
}
};
}

108
tests/programmability.py Normal file
Просмотреть файл

@ -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()