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:
Mahati Chamarthy 2022-08-08 15:21:22 +01:00 коммит произвёл GitHub
Родитель 4e813e0559
Коммит 88d3e83faa
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
11 изменённых файлов: 84 добавлений и 25 удалений

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

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