NetServices: Add support for Basic authentication

Change-Id: I304104f7096fd935212e1bfa3e988e7945cb5cec
This commit is contained in:
Niels Sascha Reedijk
2022-05-29 15:13:54 +01:00
parent f751534257
commit f9d9d20245
7 changed files with 179 additions and 4 deletions
+55
View File
@@ -308,6 +308,34 @@ namespace Network {
*/ */
/*!
\struct BHttpAuthentication
\ingroup netservices
\brief Describe username and password for basic authentication for the request.
\see These options are used by \ref BHttpRequest::Authentication() and
\ref BHttpRequest::SetAuthentication()
\since Haiku R1
*/
/*!
\var BString BHttpAuthentication::username
\brief The username for the request.
\since Haiku R1
*/
/*!
\var BString BHttpAuthentication::password
\brief The password for the request.
\since Haiku R1
*/
/*! /*!
\class BHttpRequest \class BHttpRequest
\ingroup netservices \ingroup netservices
@@ -478,6 +506,18 @@ namespace Network {
//! @{ //! @{
/*!
\fn const BHttpAuthentication* BHttpRequest::Authentication() const noexcept
\brief Get the credentials for the authentication for the request.
\return When no credentials are set for this request, the method returns a \c nullptr.
Otherwise, it will return a pointer to the current BHttpAuthentication data set for this
request.
\since Haiku R1
*/
/*! /*!
\fn const BHttpFields& BHttpRequest::Fields() const noexcept \fn const BHttpFields& BHttpRequest::Fields() const noexcept
\brief Get the additional header fields set for the request. \brief Get the additional header fields set for the request.
@@ -531,6 +571,21 @@ namespace Network {
//! @{ //! @{
/*!
\fn void BHttpRequest::SetAuthentication(const BHttpAuthentication &authentication)
\brief Set the credentials to enable basic authentication for the request.
The Basic authorization line is added to the request upon setting the request details. There is
no support for other authentication schemes, like digest authentication.
\param authentication The credentials to apply to the request.
\exception std::bad_alloc This exception may be raised if it is impossible to allocate memory.
\since Haiku R1
*/
/*! /*!
\fn void BHttpRequest::SetFields(const BHttpFields &fields) \fn void BHttpRequest::SetFields(const BHttpFields &fields)
\brief Set additional header \a fields for this request. \brief Set additional header \a fields for this request.
@@ -80,6 +80,12 @@ struct BHttpRedirectOptions {
}; };
struct BHttpAuthentication {
BString username;
BString password;
};
class BHttpRequest { class BHttpRequest {
public: public:
// Constructors and Destructor // Constructors and Destructor
@@ -95,12 +101,14 @@ public:
// Access // Access
bool IsEmpty() const noexcept; bool IsEmpty() const noexcept;
const BHttpAuthentication* Authentication() const noexcept;
const BHttpFields& Fields() const noexcept; const BHttpFields& Fields() const noexcept;
const BHttpMethod& Method() const noexcept; const BHttpMethod& Method() const noexcept;
const BHttpRedirectOptions& Redirect() const noexcept; const BHttpRedirectOptions& Redirect() const noexcept;
const BUrl& Url() const noexcept; const BUrl& Url() const noexcept;
// Named Setters // Named Setters
void SetAuthentication(const BHttpAuthentication& authentication);
void SetFields(const BHttpFields& fields); void SetFields(const BHttpFields& fields);
void SetMethod(const BHttpMethod& method); void SetMethod(const BHttpMethod& method);
void SetRedirect(const BHttpRedirectOptions& redirectOptions); void SetRedirect(const BHttpRedirectOptions& redirectOptions);
@@ -75,6 +75,9 @@ private:
}; };
BString encode_to_base64(const BString& string);
} }
} }
@@ -160,13 +160,30 @@ static const BHttpRedirectOptions kDefaultRedirectOptions = BHttpRedirectOptions
static const BHttpFields kDefaultOptionalFields = BHttpFields(); static const BHttpFields kDefaultOptionalFields = BHttpFields();
struct BHttpRequest::Data { struct BHttpRequest::Data {
BUrl url = kDefaultUrl; BUrl url = kDefaultUrl;
BHttpMethod method = kDefaultMethod; BHttpMethod method = kDefaultMethod;
BHttpRedirectOptions redirectOptions; BHttpRedirectOptions redirectOptions;
BHttpFields optionalFields; BHttpFields optionalFields;
std::optional<BHttpAuthentication> authentication;
}; };
// #pragma mark -- BHttpRequest helper functions
/*!
\brief Build basic authentication header
*/
static inline BString
build_basic_http_header(const BString& username, const BString& password)
{
BString basicEncode, result;
basicEncode << username << ":" << password;
result << "Basic " << encode_to_base64(basicEncode);
return result;
}
// #pragma mark -- BHttpRequest // #pragma mark -- BHttpRequest
@@ -201,6 +218,15 @@ BHttpRequest::IsEmpty() const noexcept
} }
const BHttpAuthentication*
BHttpRequest::Authentication() const noexcept
{
if (fData && fData->authentication)
return std::addressof(*fData->authentication);
return nullptr;
}
const BHttpFields& const BHttpFields&
BHttpRequest::Fields() const noexcept BHttpRequest::Fields() const noexcept
{ {
@@ -237,6 +263,16 @@ BHttpRequest::Url() const noexcept
} }
void
BHttpRequest::SetAuthentication(const BHttpAuthentication& authentication)
{
if (!fData)
fData = std::make_unique<Data>();
fData->authentication = authentication;
}
static constexpr std::array<std::string_view, 4> fReservedOptionalFieldNames = { static constexpr std::array<std::string_view, 4> fReservedOptionalFieldNames = {
"Host"sv, "Host"sv,
"Accept"sv, "Accept"sv,
@@ -357,6 +393,13 @@ BHttpRequest::SerializeHeaderTo(BDataIO* target) const
}); });
} }
if (fData->authentication) {
// This request will add a Basic authorization header
BString authorization = build_basic_http_header(fData->authentication->username,
fData->authentication->password);
outputFields.AddField("Authorization"sv, std::string_view(authorization.String()));
}
for (const auto& field: outputFields) { for (const auto& field: outputFields) {
bytesWritten += _write_to_dataio(target, field.RawField()); bytesWritten += _write_to_dataio(target, field.RawField());
bytesWritten += _write_to_dataio(target, "\r\n"sv); bytesWritten += _write_to_dataio(target, "\r\n"sv);
@@ -152,6 +152,52 @@ BNetworkRequestError::ErrorCode() const noexcept
} }
// #pragma mark -- Public functions
static const char* kBase64Symbols
= "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
BString
encode_to_base64(const BString& string)
{
BString result;
BString tmpString = string;
while (tmpString.Length()) {
char in[3] = { 0, 0, 0 };
char out[4] = { 0, 0, 0, 0 };
int8 remaining = tmpString.Length();
tmpString.MoveInto(in, 0, 3);
out[0] = (in[0] & 0xFC) >> 2;
out[1] = ((in[0] & 0x03) << 4) | ((in[1] & 0xF0) >> 4);
out[2] = ((in[1] & 0x0F) << 2) | ((in[2] & 0xC0) >> 6);
out[3] = in[2] & 0x3F;
for (int i = 0; i < 4; i++)
out[i] = kBase64Symbols[(int)out[i]];
// Add padding if the input length is not a multiple
// of 3
switch (remaining) {
case 1:
out[2] = '=';
// Fall through
case 2:
out[3] = '=';
break;
}
result.Append(out, 4);
}
return result;
}
// #pragma mark -- Private functions and data // #pragma mark -- Private functions and data
@@ -494,6 +494,7 @@ HttpIntegrationTest::AddTests(BTestSuite& parent)
testCaller->addThread("HeadTest", &HttpIntegrationTest::HeadTest); testCaller->addThread("HeadTest", &HttpIntegrationTest::HeadTest);
testCaller->addThread("NoContentTest", &HttpIntegrationTest::NoContentTest); testCaller->addThread("NoContentTest", &HttpIntegrationTest::NoContentTest);
testCaller->addThread("AutoRedirectTest", &HttpIntegrationTest::AutoRedirectTest); testCaller->addThread("AutoRedirectTest", &HttpIntegrationTest::AutoRedirectTest);
testCaller->addThread("BasicAuthTest", &HttpIntegrationTest::BasicAuthTest);
suite.addTest(testCaller); suite.addTest(testCaller);
parent.addTest("HttpIntegrationTest", &suite); parent.addTest("HttpIntegrationTest", &suite);
@@ -514,6 +515,7 @@ HttpIntegrationTest::AddTests(BTestSuite& parent)
testCaller->addThread("HeadTest", &HttpIntegrationTest::HeadTest); testCaller->addThread("HeadTest", &HttpIntegrationTest::HeadTest);
testCaller->addThread("NoContentTest", &HttpIntegrationTest::NoContentTest); testCaller->addThread("NoContentTest", &HttpIntegrationTest::NoContentTest);
testCaller->addThread("AutoRedirectTest", &HttpIntegrationTest::AutoRedirectTest); testCaller->addThread("AutoRedirectTest", &HttpIntegrationTest::AutoRedirectTest);
testCaller->addThread("BasicAuthTest", &HttpIntegrationTest::BasicAuthTest);
suite.addTest(testCaller); suite.addTest(testCaller);
parent.addTest("HttpsIntegrationTest", &suite); parent.addTest("HttpsIntegrationTest", &suite);
@@ -680,3 +682,20 @@ HttpIntegrationTest::AutoRedirectTest()
CPPUNIT_FAIL(e.DebugMessage().String()); CPPUNIT_FAIL(e.DebugMessage().String());
} }
} }
void
HttpIntegrationTest::BasicAuthTest()
{
// Basic Authentication
auto request = BHttpRequest(BUrl(fTestServer.BaseUrl(), "/auth/basic/walter/secret"));
request.SetAuthentication({"walter", "secret"});
auto result = fSession.Execute(std::move(request));
CPPUNIT_ASSERT(result.Status().code == 200);
// Basic Authentication with incorrect credentials
request = BHttpRequest(BUrl(fTestServer.BaseUrl(), "/auth/basic/walter/secret"));
request.SetAuthentication({"invaliduser", "invalidpassword"});
result = fSession.Execute(std::move(request));
CPPUNIT_ASSERT(result.Status().code == 401);
}
@@ -43,6 +43,7 @@ public:
void HeadTest(); void HeadTest();
void NoContentTest(); void NoContentTest();
void AutoRedirectTest(); void AutoRedirectTest();
void BasicAuthTest();
static void AddTests(BTestSuite& suite); static void AddTests(BTestSuite& suite);