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

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
\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
\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)
\brief Set additional header \a fields for this request.

View File

@ -80,6 +80,12 @@ struct BHttpRedirectOptions {
};
struct BHttpAuthentication {
BString username;
BString password;
};
class BHttpRequest {
public:
// Constructors and Destructor
@ -95,12 +101,14 @@ public:
// Access
bool IsEmpty() const noexcept;
const BHttpAuthentication* Authentication() const noexcept;
const BHttpFields& Fields() const noexcept;
const BHttpMethod& Method() const noexcept;
const BHttpRedirectOptions& Redirect() const noexcept;
const BUrl& Url() const noexcept;
// Named Setters
void SetAuthentication(const BHttpAuthentication& authentication);
void SetFields(const BHttpFields& fields);
void SetMethod(const BHttpMethod& method);
void SetRedirect(const BHttpRedirectOptions& redirectOptions);

View File

@ -75,6 +75,9 @@ private:
};
BString encode_to_base64(const BString& string);
}
}

View File

@ -160,13 +160,30 @@ static const BHttpRedirectOptions kDefaultRedirectOptions = BHttpRedirectOptions
static const BHttpFields kDefaultOptionalFields = BHttpFields();
struct BHttpRequest::Data {
BUrl url = kDefaultUrl;
BHttpMethod method = kDefaultMethod;
BHttpRedirectOptions redirectOptions;
BHttpFields optionalFields;
BUrl url = kDefaultUrl;
BHttpMethod method = kDefaultMethod;
BHttpRedirectOptions redirectOptions;
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
@ -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&
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 = {
"Host"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) {
bytesWritten += _write_to_dataio(target, field.RawField());
bytesWritten += _write_to_dataio(target, "\r\n"sv);

View File

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

View File

@ -494,6 +494,7 @@ HttpIntegrationTest::AddTests(BTestSuite& parent)
testCaller->addThread("HeadTest", &HttpIntegrationTest::HeadTest);
testCaller->addThread("NoContentTest", &HttpIntegrationTest::NoContentTest);
testCaller->addThread("AutoRedirectTest", &HttpIntegrationTest::AutoRedirectTest);
testCaller->addThread("BasicAuthTest", &HttpIntegrationTest::BasicAuthTest);
suite.addTest(testCaller);
parent.addTest("HttpIntegrationTest", &suite);
@ -514,6 +515,7 @@ HttpIntegrationTest::AddTests(BTestSuite& parent)
testCaller->addThread("HeadTest", &HttpIntegrationTest::HeadTest);
testCaller->addThread("NoContentTest", &HttpIntegrationTest::NoContentTest);
testCaller->addThread("AutoRedirectTest", &HttpIntegrationTest::AutoRedirectTest);
testCaller->addThread("BasicAuthTest", &HttpIntegrationTest::BasicAuthTest);
suite.addTest(testCaller);
parent.addTest("HttpsIntegrationTest", &suite);
@ -680,3 +682,20 @@ HttpIntegrationTest::AutoRedirectTest()
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);
}

View File

@ -43,6 +43,7 @@ public:
void HeadTest();
void NoContentTest();
void AutoRedirectTest();
void BasicAuthTest();
static void AddTests(BTestSuite& suite);