зеркало из https://github.com/microsoft/CCF.git
Populate all error details on authentication failure (#4093)
When an application is configured with multiple auth options, respond with auth specific error details on failure. Co-authored-by: Mahati Chamarthy <mchamarthy@microsoft.com>
This commit is contained in:
Родитель
4e813e0559
Коммит
88d3e83faa
|
@ -169,7 +169,6 @@ set(CCF_ENDPOINTS_SOURCES
|
|||
${CCF_DIR}/src/endpoints/base_endpoint_registry.cpp
|
||||
${CCF_DIR}/src/endpoints/common_endpoint_registry.cpp
|
||||
${CCF_DIR}/src/endpoints/json_handler.cpp
|
||||
${CCF_DIR}/src/endpoints/authentication/authentication_types.cpp
|
||||
${CCF_DIR}/src/endpoints/authentication/cert_auth.cpp
|
||||
${CCF_DIR}/src/endpoints/authentication/empty_auth.cpp
|
||||
${CCF_DIR}/src/endpoints/authentication/jwt_auth.cpp
|
||||
|
|
|
@ -35,10 +35,16 @@ namespace ccf
|
|||
std::string& error_reason) = 0;
|
||||
|
||||
virtual void set_unauthenticated_error(
|
||||
std::shared_ptr<ccf::RpcContext> ctx, std::string&& error_reason);
|
||||
std::shared_ptr<ccf::RpcContext> ctx, std::string&& error_reason)
|
||||
{}
|
||||
|
||||
virtual std::optional<OpenAPISecuritySchema> get_openapi_security_schema()
|
||||
const = 0;
|
||||
|
||||
virtual std::string get_security_scheme_name()
|
||||
{
|
||||
return "BaseAuthPolicy";
|
||||
}
|
||||
};
|
||||
|
||||
using AuthnPolicies = std::vector<std::shared_ptr<AuthnPolicy>>;
|
||||
|
|
|
@ -29,5 +29,10 @@ namespace ccf
|
|||
{
|
||||
return unauthenticated_schema;
|
||||
}
|
||||
|
||||
std::string get_security_scheme_name() override
|
||||
{
|
||||
return SECURITY_SCHEME_NAME;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -39,5 +39,10 @@ namespace ccf
|
|||
{
|
||||
return security_schema;
|
||||
}
|
||||
|
||||
std::string get_security_scheme_name() override
|
||||
{
|
||||
return SECURITY_SCHEME_NAME;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -47,6 +47,11 @@ namespace ccf
|
|||
{
|
||||
return security_schema;
|
||||
}
|
||||
|
||||
std::string get_security_scheme_name() override
|
||||
{
|
||||
return SECURITY_SCHEME_NAME;
|
||||
}
|
||||
};
|
||||
|
||||
struct MemberSignatureAuthnIdentity : public AuthnIdentity
|
||||
|
@ -90,5 +95,10 @@ namespace ccf
|
|||
{
|
||||
return security_schema;
|
||||
}
|
||||
|
||||
std::string get_security_scheme_name() override
|
||||
{
|
||||
return SECURITY_SCHEME_NAME;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -7,14 +7,28 @@
|
|||
|
||||
namespace ccf
|
||||
{
|
||||
struct ODataErrorDetails
|
||||
{
|
||||
std::string auth_policy;
|
||||
std::string code;
|
||||
std::string message;
|
||||
|
||||
bool operator==(const ODataErrorDetails&) const = default;
|
||||
};
|
||||
|
||||
DECLARE_JSON_TYPE(ODataErrorDetails);
|
||||
DECLARE_JSON_REQUIRED_FIELDS(ODataErrorDetails, auth_policy, code, message);
|
||||
|
||||
struct ODataError
|
||||
{
|
||||
std::string code;
|
||||
std::string message;
|
||||
std::vector<ODataErrorDetails> details = {};
|
||||
};
|
||||
|
||||
DECLARE_JSON_TYPE(ODataError);
|
||||
DECLARE_JSON_TYPE_WITH_OPTIONAL_FIELDS(ODataError);
|
||||
DECLARE_JSON_REQUIRED_FIELDS(ODataError, code, message);
|
||||
DECLARE_JSON_OPTIONAL_FIELDS(ODataError, details);
|
||||
|
||||
struct ODataErrorResponse
|
||||
{
|
||||
|
|
|
@ -112,6 +112,18 @@ namespace ccf
|
|||
set_response_header(name, std::to_string(n));
|
||||
}
|
||||
|
||||
/// Construct OData-formatted response to capture multiple error details
|
||||
virtual void set_error(
|
||||
http_status status,
|
||||
const std::string& code,
|
||||
std::string&& msg,
|
||||
std::vector<ccf::ODataErrorDetails>& details)
|
||||
{
|
||||
nlohmann::json body =
|
||||
ccf::ODataErrorResponse{ccf::ODataError{code, std::move(msg), details}};
|
||||
set_response(body, status);
|
||||
}
|
||||
|
||||
/// Construct OData-formatted error response.
|
||||
virtual void set_error(
|
||||
http_status status, const std::string& code, std::string&& msg)
|
||||
|
@ -124,11 +136,16 @@ namespace ccf
|
|||
{
|
||||
nlohmann::json body = ccf::ODataErrorResponse{
|
||||
ccf::ODataError{std::move(error.code), std::move(error.msg)}};
|
||||
set_response(body, error.status);
|
||||
}
|
||||
|
||||
virtual void set_response(nlohmann::json& body, http_status status)
|
||||
{
|
||||
// Set error_handler to replace, to avoid throwing if the error message
|
||||
// contains non-UTF8 characters. Other args are default values
|
||||
const auto s =
|
||||
body.dump(-1, ' ', false, nlohmann::json::error_handler_t::replace);
|
||||
set_response_status(error.status);
|
||||
set_response_status(status);
|
||||
set_response_body(std::vector<uint8_t>(s.begin(), s.end()));
|
||||
set_response_header(
|
||||
http::headers::CONTENT_TYPE, http::headervalues::contenttype::JSON);
|
||||
|
|
|
@ -126,6 +126,11 @@ namespace loggingapp
|
|||
// return nullopt
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::string get_security_scheme_name() override
|
||||
{
|
||||
return "CustomAuthPolicy";
|
||||
}
|
||||
};
|
||||
// SNIPPET_END: custom_auth_policy
|
||||
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the Apache 2.0 License.
|
||||
|
||||
#include "ccf/endpoints/authentication/authentication_types.h"
|
||||
|
||||
#include "ccf/rpc_context.h"
|
||||
|
||||
namespace ccf
|
||||
{
|
||||
void AuthnPolicy::set_unauthenticated_error(
|
||||
std::shared_ptr<ccf::RpcContext> ctx, std::string&& error_reason)
|
||||
{
|
||||
ctx->set_error(
|
||||
HTTP_STATUS_UNAUTHORIZED,
|
||||
ccf::errors::InvalidAuthenticationInfo,
|
||||
std::move(error_reason));
|
||||
}
|
||||
}
|
|
@ -258,6 +258,7 @@ namespace ccf
|
|||
if (!endpoint->authn_policies.empty())
|
||||
{
|
||||
std::string auth_error_reason;
|
||||
std::vector<ccf::ODataErrorDetails> error_details;
|
||||
for (const auto& policy : endpoint->authn_policies)
|
||||
{
|
||||
identity = policy->authenticate(tx, ctx, auth_error_reason);
|
||||
|
@ -265,13 +266,28 @@ namespace ccf
|
|||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Collate error details
|
||||
error_details.push_back(
|
||||
{policy->get_security_scheme_name(),
|
||||
ccf::errors::InvalidAuthenticationInfo,
|
||||
auth_error_reason});
|
||||
}
|
||||
}
|
||||
|
||||
if (identity == nullptr)
|
||||
{
|
||||
// If none were accepted, let the last set an error
|
||||
// If none were accepted, let the last set the response header
|
||||
endpoint->authn_policies.back()->set_unauthenticated_error(
|
||||
ctx, std::move(auth_error_reason));
|
||||
// Return collated error details for the auth policies declared
|
||||
// in the request
|
||||
ctx->set_error(
|
||||
HTTP_STATUS_UNAUTHORIZED,
|
||||
ccf::errors::InvalidAuthenticationInfo,
|
||||
"Invalid info",
|
||||
error_details);
|
||||
update_metrics(ctx, endpoint);
|
||||
return ctx->serialise_response();
|
||||
}
|
||||
|
|
|
@ -359,8 +359,8 @@ def test_invalid_client_signature(network, args):
|
|||
).json()
|
||||
assert r["error"]["code"] == "InvalidAuthenticationInfo"
|
||||
assert (
|
||||
expected_error_msg in r["error"]["message"]
|
||||
), f"Expected error message '{expected_error_msg}' not in '{r['error']['message']}'"
|
||||
expected_error_msg in r["error"]["details"][0]["message"]
|
||||
), f"Expected error message '{expected_error_msg}' not in '{r['error']['details'][0]['message']}'"
|
||||
|
||||
# Verify that _some_ HTTP signature parsing errors are communicated back to the client
|
||||
post_proposal_request_raw(
|
||||
|
|
Загрузка…
Ссылка в новой задаче