HaikuDepot: Token Based Authentication

This switches the application over from using basic
authentication to using token-based authentication in
preparation for later using Open-ID based
authentication flows. The application version is also
bumped in order that the server can detect this version
at some later date in the future when it no longer
supports basic authentication itself.

Change-Id: I7addde1d57503c58d6bcd54908f22f66830c0c59
Reviewed-on: https://review.haiku-os.org/c/haiku/+/6944
Tested-by: Commit checker robot <no-reply+buildbot@haiku-os.org>
Reviewed-by: Jérôme Duval <jerome.duval@gmail.com>
This commit is contained in:
Andrew Lindesay 2023-09-23 21:15:28 +12:00
parent d8bca3564a
commit 4b347fccb2
31 changed files with 1103 additions and 160 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2010-2014 Haiku Inc. All rights reserved.
* Copyright 2010-2023 Haiku Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*/
#ifndef _B_HTTP_AUTHENTICATION_H_
@ -24,8 +24,10 @@ enum BHttpAuthenticationMethod {
// Basic base64 authentication method (unsecure)
B_HTTP_AUTHENTICATION_DIGEST = 2,
// Digest authentication
B_HTTP_AUTHENTICATION_IE_DIGEST = 4
B_HTTP_AUTHENTICATION_IE_DIGEST = 4,
// Slightly modified digest authentication to mimic old IE one
B_HTTP_AUTHENTICATION_BEARER = 5
// Bearer authentication used to convey a token
};
@ -56,6 +58,7 @@ public:
// Field modification
void SetUserName(const BString& username);
void SetPassword(const BString& password);
void SetToken(const BString& token);
void SetMethod(
BHttpAuthenticationMethod type);
status_t Initialize(const BString& wwwAuthenticate);
@ -63,6 +66,7 @@ public:
// Field access
const BString& UserName() const;
const BString& Password() const;
const BString& Token() const;
BHttpAuthenticationMethod Method() const;
BString Authorization(const BUrl& url,
@ -89,6 +93,7 @@ private:
BHttpAuthenticationMethod fAuthenticationMethod;
BString fUserName;
BString fPassword;
BString fToken;
BString fRealm;
BString fDigestNonce;

View File

@ -8,7 +8,7 @@ resource app_flags B_SINGLE_LAUNCH;
resource app_version {
major = 0,
middle = 0,
minor = 6,
minor = 7,
variety = B_APPV_ALPHA,
internal = 1,

View File

@ -1,5 +1,5 @@
/*
* Copyright 2018-2021, Andrew Lindesay <apl@lindesay.co.nz>.
* Copyright 2018-2023, Andrew Lindesay <apl@lindesay.co.nz>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
#ifndef HAIKU_DEPOT_CONSTANTS_H
@ -66,6 +66,7 @@ enum BitmapSize {
#define HD_CLIENT_TOO_OLD (HD_ERROR_BASE + 2)
#define HD_ERR_NOT_MODIFIED (HD_ERROR_BASE + 3)
#define HD_ERR_NO_DATA (HD_ERROR_BASE + 4)
#define HD_AUTHENTICATION_FAILED (HD_ERROR_BASE + 5)
#define REPOSITORY_NAME_SYSTEM "system"

View File

@ -113,6 +113,7 @@ local textDocumentSources =
local applicationSources =
App.cpp
AccessToken.cpp
BitmapView.cpp
Captcha.cpp
CreateUserDetail.cpp
@ -124,6 +125,7 @@ local applicationSources =
IconTarPtr.cpp
IncrementViewCounterProcess.cpp
JobStateListener.cpp
JwtTokenHelper.cpp
LanguageModel.cpp
LinkView.cpp
LinkedBitmapView.cpp

View File

@ -0,0 +1,134 @@
/*
* Copyright 2023, Andrew Lindesay <apl@lindesay.co.nz>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
#include "AccessToken.h"
#include "JwtTokenHelper.h"
#include "Logger.h"
// These are keys that are used to store this object's data into a BMessage instance.
#define KEY_TOKEN "token"
#define KEY_EXPIRY_TIMESTAMP "expiryTimestamp"
AccessToken::AccessToken(BMessage* from)
:
fToken(""),
fExpiryTimestamp(0)
{
if (from->FindString(KEY_TOKEN, &fToken) != B_OK) {
HDERROR("expected key [%s] in the message data when creating an access"
" token", KEY_TOKEN);
}
if (from->FindUInt64(KEY_EXPIRY_TIMESTAMP, &fExpiryTimestamp) != B_OK) {
HDERROR("expected key [%s] in the message data when creating an access"
" token", KEY_EXPIRY_TIMESTAMP);
}
}
AccessToken::AccessToken()
:
fToken(""),
fExpiryTimestamp(0)
{
}
AccessToken::~AccessToken()
{
}
AccessToken&
AccessToken::operator=(const AccessToken& other)
{
fToken = other.fToken;
fExpiryTimestamp = other.fExpiryTimestamp;
return *this;
}
bool
AccessToken::operator==(const AccessToken& other) const
{
return fToken == other.fToken;
}
bool
AccessToken::operator!=(const AccessToken& other) const
{
return !(*this == other);
}
const BString&
AccessToken::Token() const
{
return fToken;
}
uint64
AccessToken::ExpiryTimestamp() const
{
return fExpiryTimestamp;
}
void
AccessToken::SetToken(const BString& value)
{
fToken = value;
}
void
AccessToken::SetExpiryTimestamp(uint64 value)
{
fExpiryTimestamp = value;
}
/*! The access token may have a value or may be the empty string. This method
will check that the token appears to be an access token.
*/
bool
AccessToken::IsValid() const
{
return JwtTokenHelper::IsValid(fToken);
}
bool
AccessToken::IsValid(uint64 currentTimestamp) const
{
return IsValid() && (fExpiryTimestamp == 0 || fExpiryTimestamp > currentTimestamp);
}
void
AccessToken::Clear()
{
fToken = "";
fExpiryTimestamp = 0;
}
status_t
AccessToken::Archive(BMessage* into, bool deep) const
{
status_t result = B_OK;
if (result == B_OK && into == NULL)
result = B_ERROR;
if (result == B_OK)
result = into->AddString(KEY_TOKEN, fToken);
if (result == B_OK)
result = into->AddUInt64(KEY_EXPIRY_TIMESTAMP, fExpiryTimestamp);
return result;
}

View File

@ -0,0 +1,51 @@
/*
* Copyright 2023, Andrew Lindesay <apl@lindesay.co.nz>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
#ifndef ACCESS_TOKEN_H
#define ACCESS_TOKEN_H
#include <Archivable.h>
#include <String.h>
class BPositionIO;
/*! When a user authenticates with the HDS system, the authentication API will
return a JWT access token which can then be later used with other APIs. This
object models the token. The reason why the token is modelled like
this is that the access token is not an opaque string; it contains a number
of key-value pairs that are known as "claims". Some of the claims are used to
detect, for example, when the access token has expired.
*/
class AccessToken : public BArchivable {
public:
AccessToken(BMessage* from);
AccessToken();
virtual ~AccessToken();
AccessToken& operator=(const AccessToken& other);
bool operator==(const AccessToken& other) const;
bool operator!=(const AccessToken& other) const;
const BString& Token() const;
uint64 ExpiryTimestamp() const;
void SetToken(const BString& value);
void SetExpiryTimestamp(uint64 value);
bool IsValid() const;
bool IsValid(uint64 currentTimestamp) const;
void Clear();
status_t Archive(BMessage* into, bool deep = true) const;
private:
BString fToken;
uint64 fExpiryTimestamp;
// milliseconds since epoc UTC
};
#endif // ACCESS_TOKEN_H

View File

@ -1,7 +1,7 @@
/*
* Copyright 2013-2014, Stephan Aßmus <superstippi@gmx.de>.
* Copyright 2014, Axel Dörfler <axeld@pinc-software.de>.
* Copyright 2016-2022, Andrew Lindesay <apl@lindesay.co.nz>.
* Copyright 2016-2023, Andrew Lindesay <apl@lindesay.co.nz>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
@ -567,7 +567,7 @@ Model::PopulatePackage(const PackageInfoRef& package, uint32 flags)
}
status_t status = fWebAppInterface
.RetreiveUserRatingsForPackageForDisplay(packageName,
.RetrieveUserRatingsForPackageForDisplay(packageName,
webAppRepositoryCode, webAppRepositorySourceCode, 0,
PACKAGE_INFO_MAX_USER_RATINGS, info);
if (status == B_OK) {
@ -780,19 +780,19 @@ Model::SetNickname(BString nickname)
nickname = "";
}
SetAuthorization(nickname, password, false);
SetCredentials(nickname, password, false);
}
const BString&
Model::Nickname() const
Model::Nickname()
{
return fWebAppInterface.Nickname();
}
void
Model::SetAuthorization(const BString& nickname, const BString& passwordClear,
Model::SetCredentials(const BString& nickname, const BString& passwordClear,
bool storePassword)
{
BString existingNickname = Nickname();
@ -820,7 +820,7 @@ Model::SetAuthorization(const BString& nickname, const BString& passwordClear,
}
BAutolock locker(&fLock);
fWebAppInterface.SetAuthorization(UserCredentials(nickname, passwordClear));
fWebAppInterface.SetCredentials(UserCredentials(nickname, passwordClear));
if (nickname != existingNickname)
_NotifyAuthorizationChanged();

View File

@ -1,6 +1,6 @@
/*
* Copyright 2013-2014, Stephan Aßmus <superstippi@gmx.de>.
* Copyright 2016-2021, Andrew Lindesay <apl@lindesay.co.nz>.
* Copyright 2016-2023, Andrew Lindesay <apl@lindesay.co.nz>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
#ifndef MODEL_H
@ -148,14 +148,13 @@ public:
uint32 flags);
void SetNickname(BString nickname);
const BString& Nickname() const;
void SetAuthorization(const BString& nickname,
const BString& Nickname();
void SetCredentials(const BString& nickname,
const BString& passwordClear,
bool storePassword);
WebAppInterface&
GetWebAppInterface()
{ return fWebAppInterface; }
WebAppInterface* GetWebAppInterface()
{ return &fWebAppInterface; }
status_t IconTarPath(BPath& path) const;
status_t DumpExportReferenceDataPath(BPath& path);

View File

@ -55,6 +55,31 @@ UserCredentials::~UserCredentials()
}
UserCredentials&
UserCredentials::operator=(const UserCredentials& other)
{
fNickname = other.fNickname;
fPasswordClear = other.fPasswordClear;
fIsSuccessful = other.fIsSuccessful;
return *this;
}
bool
UserCredentials::operator==(const UserCredentials& other) const
{
return fNickname == other.fNickname && fPasswordClear == other.fPasswordClear
&& fIsSuccessful == other.fIsSuccessful;
}
bool
UserCredentials::operator!=(const UserCredentials& other) const
{
return !(*this == other);
}
const BString&
UserCredentials::Nickname() const
{
@ -104,16 +129,6 @@ UserCredentials::SetIsSuccessful(bool value)
}
UserCredentials&
UserCredentials::operator=(const UserCredentials& other)
{
fNickname = other.fNickname;
fPasswordClear = other.fPasswordClear;
fIsSuccessful = other.fIsSuccessful;
return *this;
}
status_t
UserCredentials::Archive(BMessage* into, bool deep) const
{

View File

@ -25,6 +25,10 @@ public:
UserCredentials();
virtual ~UserCredentials();
UserCredentials& operator=(const UserCredentials& other);
bool operator==(const UserCredentials& other) const;
bool operator!=(const UserCredentials& other) const;
const BString& Nickname() const;
const BString& PasswordClear() const;
const bool IsSuccessful() const;
@ -34,8 +38,6 @@ public:
void SetPasswordClear(const BString& value);
void SetIsSuccessful(bool value);
UserCredentials& operator=(const UserCredentials& other);
status_t Archive(BMessage* into, bool deep = true) const;
private:
BString fNickname;

View File

@ -1,5 +1,5 @@
/*
* Copyright 2021-2022, Andrew Lindesay <apl@lindesay.co.nz>.
* Copyright 2021-2023, Andrew Lindesay <apl@lindesay.co.nz>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
#include "IncrementViewCounterProcess.h"
@ -81,13 +81,11 @@ IncrementViewCounterProcess::RunInternal()
while (attempts > 0 && !WasStopped()) {
BMessage resultEnvelope;
WebAppInterface& webAppInterface = fModel->GetWebAppInterface();
result = webAppInterface.IncrementViewCounter(fPackage, depot,
resultEnvelope);
WebAppInterface* webAppInterface = fModel->GetWebAppInterface();
result = webAppInterface->IncrementViewCounter(fPackage, depot, resultEnvelope);
if (result == B_OK) {
int32 errorCode = webAppInterface.ErrorCodeFromResponse(
resultEnvelope);
int32 errorCode = WebAppInterface::ErrorCodeFromResponse(resultEnvelope);
switch (errorCode) {
case ERROR_CODE_NONE:
HDINFO("did increment the view counter for [%s]",

View File

@ -0,0 +1,78 @@
/*
* Copyright 2023, Andrew Lindesay <apl@lindesay.co.nz>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
#include "JwtTokenHelper.h"
#include "DataIOUtils.h"
#include "Json.h"
#include "JsonMessageWriter.h"
#include <ctype.h>
/*static*/ bool
JwtTokenHelper::IsValid(const BString& value)
{
int countDots = 0;
for (int i = 0; i < value.Length(); i++) {
char ch = value[i];
if ('.' == ch)
countDots++;
else {
if (!_IsBase64(ch))
return false;
}
}
return 2 == countDots;
}
/*! A JWT token is split into three parts separated by a '.' character. The
middle section is a base-64 encoded string and within the string is JSON
structured data. The JSON data contains key-value pairs which carry data
about the token. This method will take a JWT token, will find the middle
section and will parse the claims into the supplied 'message' parameter.
*/
/*static*/ status_t
JwtTokenHelper::ParseClaims(const BString& token, BMessage& message)
{
int firstDot = token.FindFirst('.');
if (firstDot == B_ERROR)
return B_BAD_VALUE;
// find the end of the first section by looking for the next dot.
int secondDot = token.FindFirst('.', firstDot + 1);
if (secondDot == B_ERROR)
return B_BAD_VALUE;
BMemoryIO memoryIo(&(token.String())[firstDot + 1], (secondDot - firstDot) - 1);
Base64DecodingDataIO base64DecodingIo(&memoryIo, '-', '_');
BJsonMessageWriter writer(message);
BJson::Parse(&base64DecodingIo, &writer);
return writer.ErrorStatus();
}
/*! Note this is base64 "url" standard that disallows "/" and "+" and instead
uses "-" and "_".
*/
/*static*/ bool
JwtTokenHelper::_IsBase64(char ch)
{
return isalnum(ch)
|| '=' == ch
|| '-' == ch
|| '_' == ch;
}

View File

@ -0,0 +1,37 @@
/*
* Copyright 2023, Andrew Lindesay <apl@lindesay.co.nz>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
#ifndef JWT_TOKEN_HELPER_H
#define JWT_TOKEN_HELPER_H
#include <String.h>
class BMessage;
/*! The term "JWT" is short for "Java Web Token" and is a token format typically used to convey
proof of an authentication. The token is structured and contains three parts separated by the
full-stop character ".". The first part is a header, the middle part contains some data (which
is termed the "claims") and the last part is a signature proving the authenticity of the claims.
The claims are base-64 encoded JSON and the JSON data typically conveys some key-value pairs
with a number of the key names being well-known through standards.
This class contains a number of helper methods for working with JWT tokens.
*/
class JwtTokenHelper {
public:
static bool IsValid(const BString& value);
static status_t ParseClaims(const BString& token,
BMessage& message);
private:
static bool _IsBase64(char ch);
};
#endif // JWT_TOKEN_HELPER_H

View File

@ -99,18 +99,18 @@ UserDetailVerifierProcess::_ShouldVerify()
status_t
UserDetailVerifierProcess::_TryFetchUserDetail(UserDetail& userDetail)
{
WebAppInterface interface = fModel->GetWebAppInterface();
WebAppInterface* interface = fModel->GetWebAppInterface();
BMessage userDetailResponse;
status_t result;
result = interface.RetrieveCurrentUserDetail(userDetailResponse);
result = interface->RetrieveCurrentUserDetail(userDetailResponse);
if (result != B_OK) {
HDERROR("a problem has arisen retrieving the current user detail: %s",
strerror(result));
}
if (result == B_OK) {
int32 errorCode = interface.ErrorCodeFromResponse(userDetailResponse);
int32 errorCode = WebAppInterface::ErrorCodeFromResponse(userDetailResponse);
switch (errorCode) {
case ERROR_CODE_NONE:
break;
@ -132,7 +132,7 @@ UserDetailVerifierProcess::_TryFetchUserDetail(UserDetail& userDetail)
// worked, it is now necessary to check to see that the user has agreed
// to the most recent user-usage conditions.
result = interface.UnpackUserDetail(userDetailResponse, userDetail);
result = interface->UnpackUserDetail(userDetailResponse, userDetail);
if (result != B_OK)
HDERROR("it was not possible to unpack the user details.");
}

View File

@ -6,21 +6,24 @@
#include "WebAppInterface.h"
#include <AutoDeleter.h>
#include <Application.h>
#include <Message.h>
#include <Url.h>
#include <AutoDeleter.h>
#include <AutoLocker.h>
#include <HttpHeaders.h>
#include <HttpRequest.h>
#include <Json.h>
#include <JsonTextWriter.h>
#include <JsonMessageWriter.h>
#include <Message.h>
#include <Url.h>
#include <UrlContext.h>
#include <UrlProtocolListener.h>
#include <UrlProtocolRoster.h>
#include "DataIOUtils.h"
#include "HaikuDepotConstants.h"
#include "JwtTokenHelper.h"
#include "Logger.h"
#include "ServerSettings.h"
#include "ServerHelper.h"
@ -103,10 +106,6 @@ make_http_request(const BUrl& url, BDataIO* output,
}
int
WebAppInterface::fRequestIndex = 0;
enum {
NEEDS_AUTHORIZATION = 1 << 0,
};
@ -117,38 +116,26 @@ WebAppInterface::WebAppInterface()
}
WebAppInterface::WebAppInterface(const WebAppInterface& other)
:
fCredentials(other.fCredentials)
{
}
WebAppInterface::~WebAppInterface()
{
}
WebAppInterface&
WebAppInterface::operator=(const WebAppInterface& other)
{
if (this == &other)
return *this;
fCredentials = other.fCredentials;
return *this;
}
void
WebAppInterface::SetAuthorization(const UserCredentials& value)
WebAppInterface::SetCredentials(const UserCredentials& value)
{
fCredentials = value;
AutoLocker<BLocker> lock(&fLock);
if (fCredentials != value) {
fCredentials = value;
fAccessToken.Clear();
}
}
const BString&
WebAppInterface::Nickname() const
WebAppInterface::Nickname()
{
AutoLocker<BLocker> lock(&fLock);
return fCredentials.Nickname();
}
@ -172,7 +159,7 @@ WebAppInterface::GetChangelog(const BString& packageName, BMessage& message)
status_t
WebAppInterface::RetreiveUserRatingsForPackageForDisplay(
WebAppInterface::RetrieveUserRatingsForPackageForDisplay(
const BString& packageName,
const BString& webAppRepositoryCode,
const BString& webAppRepositorySourceCode,
@ -209,7 +196,7 @@ WebAppInterface::RetreiveUserRatingsForPackageForDisplay(
status_t
WebAppInterface::RetreiveUserRatingForPackageAndVersionByUser(
WebAppInterface::RetrieveUserRatingForPackageAndVersionByUser(
const BString& packageName, const BPackageVersion& version,
const BString& architecture,
const BString& webAppRepositoryCode,
@ -283,19 +270,40 @@ WebAppInterface::RetrieveUserDetailForCredentials(
"to obtain the user detail");
}
// BHttpRequest later takes ownership of this.
BMallocIO* requestEnvelopeData = new BMallocIO();
BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData);
status_t result = B_OK;
requestEnvelopeWriter.WriteObjectStart();
requestEnvelopeWriter.WriteObjectName("nickname");
requestEnvelopeWriter.WriteString(credentials.Nickname().String());
requestEnvelopeWriter.WriteObjectEnd();
// authenticate the user and obtain a token to use with the latter
// request.
status_t result = _SendJsonRequest("user/get-user", credentials,
requestEnvelopeData, _LengthAndSeekToZero(requestEnvelopeData),
NEEDS_AUTHORIZATION, message);
// note that the credentials used here are passed in as args.
BMessage authenticateResponseEnvelopeMessage;
if (result == B_OK) {
result = AuthenticateUser(
credentials.Nickname(),
credentials.PasswordClear(),
authenticateResponseEnvelopeMessage);
}
AccessToken accessToken;
if (result == B_OK)
result = UnpackAccessToken(authenticateResponseEnvelopeMessage, accessToken);
if (result == B_OK) {
// BHttpRequest later takes ownership of this.
BMallocIO* requestEnvelopeData = new BMallocIO();
BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData);
requestEnvelopeWriter.WriteObjectStart();
requestEnvelopeWriter.WriteObjectName("nickname");
requestEnvelopeWriter.WriteString(credentials.Nickname().String());
requestEnvelopeWriter.WriteObjectEnd();
result = _SendJsonRequest("user/get-user", accessToken,
requestEnvelopeData, _LengthAndSeekToZero(requestEnvelopeData),
NEEDS_AUTHORIZATION, message);
// note that the credentials used here are passed in as args.
}
return result;
}
@ -308,7 +316,8 @@ WebAppInterface::RetrieveUserDetailForCredentials(
status_t
WebAppInterface::RetrieveCurrentUserDetail(BMessage& message)
{
return RetrieveUserDetailForCredentials(fCredentials, message);
UserCredentials credentials = _Credentials();
return RetrieveUserDetailForCredentials(credentials, message);
}
@ -366,6 +375,66 @@ WebAppInterface::UnpackUserDetail(BMessage& responseEnvelopeMessage,
}
/*! When an authentication API call is made, the response (if successful) will
return an access token in the response. This method will take the response
from the server and will parse out the access token data into the supplied
object.
*/
/*static*/ status_t
WebAppInterface::UnpackAccessToken(BMessage& responseEnvelopeMessage,
AccessToken& accessToken)
{
status_t result;
BMessage resultMessage;
result = responseEnvelopeMessage.FindMessage(
"result", &resultMessage);
if (result != B_OK) {
HDERROR("bad response envelope missing 'result' entry");
return result;
}
BString token;
result = resultMessage.FindString("token", &token);
if (result != B_OK || token.IsEmpty()) {
HDINFO("failure to authenticate");
return HD_AUTHENTICATION_FAILED;
}
// The token should be present in three parts; the header, the claims and
// then a digital signature. The logic here wants to extract some data
// from the claims part.
BMessage claimsMessage;
result = JwtTokenHelper::ParseClaims(token, claimsMessage);
if (Logger::IsTraceEnabled()) {
HDTRACE("start; token claims...");
claimsMessage.PrintToStream();
HDTRACE("...end; token claims");
}
if (B_OK == result) {
accessToken.SetToken(token);
accessToken.SetExpiryTimestamp(0);
double expiryTimestampDouble;
// The claims should have parsed but it could transpire that there is
// no expiry. This should not be the case, but it is theoretically
// possible.
if (claimsMessage.FindDouble("exp", &expiryTimestampDouble) == B_OK)
accessToken.SetExpiryTimestamp(1000 * static_cast<uint64>(expiryTimestampDouble));
}
return result;
}
/*! \brief Returns data relating to the user usage conditions
\param code defines the version of the data to return or if empty then the
@ -432,7 +501,7 @@ WebAppInterface::AgreeUserUsageConditions(const BString& code,
requestEnvelopeWriter.WriteObjectName("userUsageConditionsCode");
requestEnvelopeWriter.WriteString(code.String());
requestEnvelopeWriter.WriteObjectName("nickname");
requestEnvelopeWriter.WriteString(fCredentials.Nickname());
requestEnvelopeWriter.WriteString(Nickname());
requestEnvelopeWriter.WriteObjectEnd();
// now fetch this information into an object.
@ -504,7 +573,7 @@ WebAppInterface::CreateUserRating(const BString& packageName,
requestEnvelopeWriter.WriteObjectName("pkgVersionType");
requestEnvelopeWriter.WriteString("SPECIFIC");
requestEnvelopeWriter.WriteObjectName("userNickname");
requestEnvelopeWriter.WriteString(fCredentials.Nickname());
requestEnvelopeWriter.WriteString(Nickname());
if (!version.Major().IsEmpty()) {
requestEnvelopeWriter.WriteObjectName("pkgVersionMajor");
@ -668,6 +737,47 @@ WebAppInterface::CreateUser(const BString& nickName,
}
/*! This method will authenticate the user set in the credentials and will
retain the resultant access token for authenticating any latter API calls.
*/
status_t
WebAppInterface::AuthenticateUserRetainingAccessToken()
{
UserCredentials userCredentials = _Credentials();
if (!userCredentials.IsValid()) {
HDINFO("unable to get a new access token as there are no credentials");
return B_NOT_INITIALIZED;
}
return _AuthenticateUserRetainingAccessToken(userCredentials.Nickname(),
userCredentials.PasswordClear());
}
status_t
WebAppInterface::_AuthenticateUserRetainingAccessToken(const BString& nickName,
const BString& passwordClear) {
AutoLocker<BLocker> lock(&fLock);
fAccessToken.Clear();
BMessage responseEnvelopeMessage;
status_t result = AuthenticateUser(nickName, passwordClear, responseEnvelopeMessage);
AccessToken accessToken;
if (result == B_OK)
result = UnpackAccessToken(responseEnvelopeMessage, accessToken);
if (result == B_OK)
fAccessToken = accessToken;
return result;
}
status_t
WebAppInterface::AuthenticateUser(const BString& nickName,
const BString& passwordClear, BMessage& message)
@ -800,7 +910,7 @@ WebAppInterface::_RetrievePasswordRequirementsMeta(BMessage& message)
not look like an error.
*/
int32
/*static*/ int32
WebAppInterface::ErrorCodeFromResponse(BMessage& responseEnvelopeMessage)
{
BMessage error;
@ -821,17 +931,23 @@ WebAppInterface::ErrorCodeFromResponse(BMessage& responseEnvelopeMessage)
status_t
WebAppInterface::_SendJsonRequest(const char* urlPathComponents,
BPositionIO* requestData, size_t requestDataSize, uint32 flags,
BMessage& reply) const
BMessage& reply)
{
return _SendJsonRequest(urlPathComponents, fCredentials, requestData,
bool needsAuthorization = (flags & NEEDS_AUTHORIZATION) != 0;
AccessToken accessToken;
if (needsAuthorization)
accessToken = _ObtainValidAccessToken();
return _SendJsonRequest(urlPathComponents, accessToken, requestData,
requestDataSize, flags, reply);
}
status_t
/*static*/ status_t
WebAppInterface::_SendJsonRequest(const char* urlPathComponents,
UserCredentials credentials, BPositionIO* requestData,
size_t requestDataSize, uint32 flags, BMessage& reply) const
const AccessToken& accessToken, BPositionIO* requestData,
size_t requestDataSize, uint32 flags, BMessage& reply)
{
if (requestDataSize == 0) {
HDINFO("%s; empty request payload", PROTOCOL_NAME);
@ -852,6 +968,15 @@ WebAppInterface::_SendJsonRequest(const char* urlPathComponents,
return HD_CLIENT_TOO_OLD;
}
bool needsAuthorization = (flags & NEEDS_AUTHORIZATION) != 0;
if (needsAuthorization && !accessToken.IsValid()) {
HDDEBUG("%s; dropping request to ...[%s] as access token is not valid",
PROTOCOL_NAME, urlPathComponents);
delete requestData;
return B_NOT_ALLOWED;
}
BUrl url = ServerSettings::CreateFullUrl(BString("/__api/v2/")
<< urlPathComponents);
HDDEBUG("%s; will make request to [%s]", PROTOCOL_NAME,
@ -883,13 +1008,10 @@ WebAppInterface::_SendJsonRequest(const char* urlPathComponents,
request->SetMethod(B_HTTP_POST);
request->SetHeaders(headers);
// Authentication via Basic Authentication
// The other way would be to obtain a token and then use the Token Bearer
// header.
if (((flags & NEEDS_AUTHORIZATION) != 0) && credentials.IsValid()) {
BHttpAuthentication authentication(credentials.Nickname(),
credentials.PasswordClear());
authentication.SetMethod(B_HTTP_AUTHENTICATION_BASIC);
if (needsAuthorization) {
BHttpAuthentication authentication;
authentication.SetMethod(B_HTTP_AUTHENTICATION_BEARER);
authentication.SetToken(accessToken.Token());
context.AddAuthentication(url, authentication);
}
@ -948,7 +1070,7 @@ WebAppInterface::_SendJsonRequest(const char* urlPathComponents,
status_t
WebAppInterface::_SendJsonRequest(const char* urlPathComponents,
const BString& jsonString, uint32 flags, BMessage& reply) const
const BString& jsonString, uint32 flags, BMessage& reply)
{
// gets 'adopted' by the subsequent http request.
BMemoryIO* data = new BMemoryIO(jsonString.String(),
@ -1042,3 +1164,32 @@ WebAppInterface::_LengthAndSeekToZero(BPositionIO* data)
data->Seek(0, SEEK_SET);
return dataSize;
}
UserCredentials
WebAppInterface::_Credentials()
{
AutoLocker<BLocker> lock(&fLock);
return fCredentials;
}
AccessToken
WebAppInterface::_ObtainValidAccessToken()
{
AutoLocker<BLocker> lock(&fLock);
uint64 now = static_cast<uint64>(time(NULL)) * 1000;
if (!fAccessToken.IsValid(now)) {
HDINFO("clearing cached access token as it is no longer valid");
fAccessToken.Clear();
}
if (!fAccessToken.IsValid()) {
HDINFO("no cached access token present; will obtain a new one");
AuthenticateUserRetainingAccessToken();
}
return fAccessToken;
}

View File

@ -9,9 +9,11 @@
#include <Application.h>
#include <JsonWriter.h>
#include <Locker.h>
#include <String.h>
#include <package/PackageVersion.h>
#include "AccessToken.h"
#include "PackageInfo.h"
#include "PasswordRequirements.h"
#include "UserCredentials.h"
@ -43,26 +45,23 @@ using BPackageKit::BPackageVersion;
class WebAppInterface {
public:
WebAppInterface();
WebAppInterface(const WebAppInterface& other);
virtual ~WebAppInterface();
WebAppInterface& operator=(const WebAppInterface& other);
void SetAuthorization(const UserCredentials& value);
const BString& Nickname() const;
void SetCredentials(const UserCredentials& value);
const BString& Nickname();
status_t GetChangelog(
const BString& packageName,
BMessage& message);
status_t RetreiveUserRatingsForPackageForDisplay(
status_t RetrieveUserRatingsForPackageForDisplay(
const BString& packageName,
const BString& webAppRepositoryCode,
const BString& webAppRepositorySourceCode,
int resultOffset, int maxResults,
BMessage& message);
status_t RetreiveUserRatingForPackageAndVersionByUser(
status_t RetrieveUserRatingForPackageAndVersionByUser(
const BString& packageName,
const BPackageVersion& version,
const BString& architecture,
@ -121,6 +120,8 @@ public:
const BString& userUsageConditionsCode,
BMessage& message);
status_t AuthenticateUserRetainingAccessToken();
status_t AuthenticateUser(const BString& nickName,
const BString& passwordClear,
BMessage& message);
@ -139,7 +140,17 @@ public:
static status_t UnpackUserDetail(
BMessage& responseEnvelopeMessage,
UserDetail& userDetail);
static status_t UnpackAccessToken(
BMessage& responseEnvelopeMessage,
AccessToken& accessToken);
private:
UserCredentials _Credentials();
AccessToken _ObtainValidAccessToken();
status_t _AuthenticateUserRetainingAccessToken(const BString& nickName,
const BString& passwordClear);
status_t _RetrievePasswordRequirementsMeta(
BMessage& message);
@ -151,16 +162,16 @@ private:
status_t _SendJsonRequest(const char* urlPathComponents,
const BString& jsonString, uint32 flags,
BMessage& reply) const;
status_t _SendJsonRequest(const char* urlPathComponents,
UserCredentials credentials,
BPositionIO* requestData,
size_t requestDataSize, uint32 flags,
BMessage& reply) const;
BMessage& reply);
status_t _SendJsonRequest(const char* urlPathComponents,
BPositionIO* requestData,
size_t requestDataSize, uint32 flags,
BMessage& reply) const;
BMessage& reply);
static status_t _SendJsonRequest(const char* urlPathComponents,
const AccessToken& accessToken,
BPositionIO* requestData,
size_t requestDataSize, uint32 flags,
BMessage& reply);
status_t _SendRawGetRequest(
const BString urlPathComponents,
@ -171,7 +182,8 @@ private:
private:
UserCredentials fCredentials;
static int fRequestIndex;
AccessToken fAccessToken;
BLocker fLock;
};

View File

@ -3,7 +3,7 @@
* Copyright 2013-2014, Stephan Aßmus <superstippi@gmx.de>.
* Copyright 2013, Rene Gollent, rene@gollent.com.
* Copyright 2013, Ingo Weinhold, ingo_weinhold@gmx.de.
* Copyright 2016-2022, Andrew Lindesay <apl@lindesay.co.nz>.
* Copyright 2016-2023, Andrew Lindesay <apl@lindesay.co.nz>.
* Copyright 2017, Julian Harnath <julian.harnath@rwth-aachen.de>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
@ -653,7 +653,7 @@ main_window_str_to_package_list_view_mode(const BString& str)
void
MainWindow::StoreSettings(BMessage& settings) const
MainWindow::StoreSettings(BMessage& settings)
{
settings.AddRect(_WindowFrameName(), Frame());
if (!fSinglePackageMode) {

View File

@ -2,7 +2,7 @@
* Copyright 2013-2014, Stephan Aßmus <superstippi@gmx.de>.
* Copyright 2013, Rene Gollent <rene@gollent.com>.
* Copyright 2017, Julian Harnath <julian.harnath@rwth-aachen.de>.
* Copyright 2017-2022, Andrew Lindesay <apl@lindesay.co.nz>.
* Copyright 2017-2023, Andrew Lindesay <apl@lindesay.co.nz>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
#ifndef MAIN_WINDOW_H
@ -48,7 +48,7 @@ public:
virtual bool QuitRequested();
virtual void MessageReceived(BMessage* message);
void StoreSettings(BMessage& message) const;
void StoreSettings(BMessage& message);
// ProcessCoordinatorConsumer
virtual void Consume(ProcessCoordinator *item);

View File

@ -1,6 +1,6 @@
/*
* Copyright 2014, Stephan Aßmus <superstippi@gmx.de>.
* Copyright 2016-2022, Andrew Lindesay <apl@lindesay.co.nz>.
* Copyright 2016-2023, Andrew Lindesay <apl@lindesay.co.nz>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
@ -544,7 +544,8 @@ RatePackageWindow::_QueryRatingThread()
return;
}
WebAppInterface interface;
WebAppInterface* interface = fModel.GetWebAppInterface();
BMessage info;
const DepotInfo* depot = fModel.DepotForName(package->DepotName());
BString webAppRepositoryCode;
@ -561,16 +562,15 @@ RatePackageWindow::_QueryRatingThread()
"code for depot; %s", package->DepotName().String());
BMessenger(this).SendMessage(B_QUIT_REQUESTED);
} else {
status_t status = interface
.RetreiveUserRatingForPackageAndVersionByUser(package->Name(),
package->Version(), package->Architecture(),
status_t status = interface->RetrieveUserRatingForPackageAndVersionByUser(
package->Name(), package->Version(), package->Architecture(),
webAppRepositoryCode, webAppRepositorySourceCode,
nickname, info);
if (status == B_OK) {
// could be an error or could be a valid response envelope
// containing data.
switch (interface.ErrorCodeFromResponse(info)) {
switch (WebAppInterface::ErrorCodeFromResponse(info)) {
case ERROR_CODE_NONE:
{
//info.PrintToStream();
@ -647,7 +647,7 @@ RatePackageWindow::_SendRatingThread()
webAppRepositorySourceCode = depot->WebAppRepositorySourceCode();
}
WebAppInterface interface = fModel.GetWebAppInterface();
WebAppInterface* interface = fModel.GetWebAppInterface();
Unlock();
@ -672,11 +672,11 @@ RatePackageWindow::_SendRatingThread()
BMessage info;
if (ratingID.Length() > 0) {
HDINFO("will update the existing user rating [%s]", ratingID.String());
status = interface.UpdateUserRating(ratingID,
status = interface->UpdateUserRating(ratingID,
languageCode, comment, stability, rating, active, info);
} else {
HDINFO("will create a new user rating for pkg [%s]", package.String());
status = interface.CreateUserRating(package, fPackage->Version(),
status = interface->CreateUserRating(package, fPackage->Version(),
architecture, webAppRepositoryCode, webAppRepositorySourceCode,
languageCode, comment, stability, rating, info);
}
@ -684,7 +684,7 @@ RatePackageWindow::_SendRatingThread()
if (status == B_OK) {
// could be an error or could be a valid response envelope
// containing data.
switch (interface.ErrorCodeFromResponse(info)) {
switch (WebAppInterface::ErrorCodeFromResponse(info)) {
case ERROR_CODE_NONE:
{
if (ratingID.Length() > 0)

View File

@ -293,9 +293,9 @@ void
ToLatestUserUsageConditionsWindow::_FetchDataPerform()
{
UserUsageConditions conditions;
WebAppInterface interface = fModel.GetWebAppInterface();
WebAppInterface* interface = fModel.GetWebAppInterface();
if (interface.RetrieveUserUsageConditions("", conditions) == B_OK) {
if (interface->RetrieveUserUsageConditions("", conditions) == B_OK) {
BMessage userUsageConditionsMessage;
conditions.Archive(&userUsageConditionsMessage, true);
BMessage dataMessage(MSG_USER_USAGE_CONDITIONS_DATA);
@ -362,16 +362,15 @@ ToLatestUserUsageConditionsWindow::_AgreePerform()
{
BMessenger messenger(this);
BMessage responsePayload;
WebAppInterface webApp = fModel.GetWebAppInterface();
status_t result = webApp.AgreeUserUsageConditions(
WebAppInterface* webApp = fModel.GetWebAppInterface();
status_t result = webApp->AgreeUserUsageConditions(
fUserUsageConditions.Code(), responsePayload);
if (result != B_OK) {
ServerHelper::NotifyTransportError(result);
messenger.SendMessage(MSG_AGREE_FAILED);
} else {
int32 errorCode = WebAppInterface::ErrorCodeFromResponse(
responsePayload);
int32 errorCode = WebAppInterface::ErrorCodeFromResponse(responsePayload);
if (errorCode == ERROR_CODE_NONE) {
AppUtils::NotifySimpleError(
B_TRANSLATE("Usage conditions agreed"),

View File

@ -518,14 +518,14 @@ void
UserLoginWindow::_AuthenticateThread(UserCredentials& userCredentials)
{
BMessage responsePayload;
WebAppInterface interface = fModel.GetWebAppInterface();
status_t status = interface.AuthenticateUser(
WebAppInterface* interface = fModel.GetWebAppInterface();
status_t status = interface->AuthenticateUser(
userCredentials.Nickname(), userCredentials.PasswordClear(),
responsePayload);
BString token;
if (status == B_OK) {
int32 errorCode = interface.ErrorCodeFromResponse(responsePayload);
int32 errorCode = WebAppInterface::ErrorCodeFromResponse(responsePayload);
if (errorCode == ERROR_CODE_NONE)
_UnpackAuthenticationToken(responsePayload, token);
@ -648,7 +648,7 @@ UserLoginWindow::_TakeUpCredentialsAndQuit(const UserCredentials& credentials)
{
{
AutoLocker<BLocker> locker(fModel.Lock());
fModel.SetAuthorization(credentials.Nickname(),
fModel.SetCredentials(credentials.Nickname(),
credentials.PasswordClear(), true);
}
@ -815,9 +815,8 @@ status_t
UserLoginWindow::_CreateAccountUserUsageConditionsSetupThread(
UserUsageConditions& userUsageConditions)
{
WebAppInterface interface = fModel.GetWebAppInterface();
status_t result = interface.RetrieveUserUsageConditions(
NULL, userUsageConditions);
WebAppInterface* interface = fModel.GetWebAppInterface();
status_t result = interface->RetrieveUserUsageConditions(NULL, userUsageConditions);
if (result != B_OK) {
AppUtils::NotifySimpleError(
@ -836,9 +835,8 @@ status_t
UserLoginWindow::_CreateAccountPasswordRequirementsSetupThread(
PasswordRequirements& passwordRequirements)
{
WebAppInterface interface = fModel.GetWebAppInterface();
status_t result = interface.RetrievePasswordRequirements(
passwordRequirements);
WebAppInterface* interface = fModel.GetWebAppInterface();
status_t result = interface->RetrievePasswordRequirements(passwordRequirements);
if (result != B_OK) {
AppUtils::NotifySimpleError(
@ -856,10 +854,10 @@ UserLoginWindow::_CreateAccountPasswordRequirementsSetupThread(
status_t
UserLoginWindow::_CreateAccountCaptchaSetupThread(Captcha& captcha)
{
WebAppInterface interface = fModel.GetWebAppInterface();
WebAppInterface* interface = fModel.GetWebAppInterface();
BMessage responsePayload;
status_t status = interface.RequestCaptcha(responsePayload);
status_t status = interface->RequestCaptcha(responsePayload);
// check for transport related errors.
@ -873,7 +871,7 @@ UserLoginWindow::_CreateAccountCaptchaSetupThread(Captcha& captcha)
// check for server-generated errors.
if (status == B_OK) {
if (interface.ErrorCodeFromResponse(responsePayload)
if (WebAppInterface::ErrorCodeFromResponse(responsePayload)
!= ERROR_CODE_NONE) {
ServerHelper::AlertTransportError(&responsePayload);
status = B_ERROR;
@ -1307,11 +1305,11 @@ UserLoginWindow::_CreateAccountThreadEntry(void* data)
void
UserLoginWindow::_CreateAccountThread(CreateUserDetail* detail)
{
WebAppInterface interface = fModel.GetWebAppInterface();
WebAppInterface* interface = fModel.GetWebAppInterface();
BMessage responsePayload;
BMessenger messenger(this);
status_t status = interface.CreateUser(
status_t status = interface->CreateUser(
detail->Nickname(),
detail->PasswordClear(),
detail->Email(),
@ -1325,7 +1323,7 @@ UserLoginWindow::_CreateAccountThread(CreateUserDetail* detail)
"There was a puzzling response from the web service.");
if (status == B_OK) {
int32 errorCode = interface.ErrorCodeFromResponse(responsePayload);
int32 errorCode = WebAppInterface::ErrorCodeFromResponse(responsePayload);
switch (errorCode) {
case ERROR_CODE_NONE:

View File

@ -270,7 +270,7 @@ UserUsageConditionsWindow::_FetchDataPerform()
{
UserDetail userDetail;
UserUsageConditions conditions;
WebAppInterface interface = fModel.GetWebAppInterface();
WebAppInterface* interface = fModel.GetWebAppInterface();
BString code;
status_t status = _FetchUserUsageConditionsCodePerform(userDetail, code);
@ -291,7 +291,7 @@ UserUsageConditionsWindow::_FetchDataPerform()
}
if (status == B_OK) {
if (interface.RetrieveUserUsageConditions(code, conditions) == B_OK) {
if (interface->RetrieveUserUsageConditions(code, conditions) == B_OK) {
BMessage userUsageConditionsMessage;
BMessage userDetailMessage;
conditions.Archive(&userUsageConditionsMessage, true);
@ -334,20 +334,19 @@ status_t
UserUsageConditionsWindow::_FetchUserUsageConditionsCodeForUserPerform(
UserDetail& userDetail, BString& code)
{
WebAppInterface interface = fModel.GetWebAppInterface();
WebAppInterface* interface = fModel.GetWebAppInterface();
if (interface.Nickname().IsEmpty())
if (interface->Nickname().IsEmpty())
debugger("attempt to get user details for the current user, but"
" there is no current user");
BMessage responseEnvelopeMessage;
status_t result = interface.RetrieveCurrentUserDetail(
responseEnvelopeMessage);
status_t result = interface->RetrieveCurrentUserDetail(responseEnvelopeMessage);
if (result == B_OK) {
// could be an error or could be a valid response envelope
// containing data.
switch (interface.ErrorCodeFromResponse(responseEnvelopeMessage)) {
switch (WebAppInterface::ErrorCodeFromResponse(responseEnvelopeMessage)) {
case ERROR_CODE_NONE:
result = WebAppInterface::UnpackUserDetail(
responseEnvelopeMessage, userDetail);
@ -368,12 +367,12 @@ UserUsageConditionsWindow::_FetchUserUsageConditionsCodeForUserPerform(
if (result == B_OK) {
BString userUsageConditionsCode = userDetail.Agreement().Code();
HDDEBUG("the user [%s] has agreed to uuc [%s]",
interface.Nickname().String(),
interface->Nickname().String(),
userUsageConditionsCode.String());
code.SetTo(userUsageConditionsCode);
} else {
HDDEBUG("unable to get details of the user [%s]",
interface.Nickname().String());
interface->Nickname().String());
}
return result;

View File

@ -1,5 +1,5 @@
/*
* Copyright 2018-2020, Andrew Lindesay <apl@lindesay.co.nz>.
* Copyright 2018-2023, Andrew Lindesay <apl@lindesay.co.nz>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
#include "DataIOUtils.h"
@ -31,6 +31,8 @@ DataIOUtils::CopyAll(BDataIO* target, BDataIO* source)
}
// #pragma mark - ConstraintedDataIO
ConstraintedDataIO::ConstraintedDataIO(BDataIO* delegate, size_t limit)
:
fDelegate(delegate),
@ -71,3 +73,120 @@ ConstraintedDataIO::Flush()
{
return B_OK;
}
// #pragma mark - Base64DecodingDataIO
Base64DecodingDataIO::Base64DecodingDataIO(BDataIO* delegate, char char62, char char63)
:
fDelegate(delegate),
fChar62(char62),
fChar63(char63),
fNextByteAssembly(0),
fNextByteAssemblyBits(0)
{
}
Base64DecodingDataIO::~Base64DecodingDataIO()
{
}
status_t
Base64DecodingDataIO::_CharToInt(uint8 ch, uint8* value)
{
if (ch >= 0x41 && ch <= 0x5A) {
*value = (ch - 0x41);
return B_OK;
}
if (ch >= 0x61 && ch <= 0x7a) {
*value = (ch - 0x61) + 26;
return B_OK;
}
if (ch >= 0x30 && ch <= 0x39) {
*value = (ch - 0x30) + 52;
return B_OK;
}
if (ch == fChar62) {
*value = 62;
return B_OK;
}
if (ch == fChar63) {
*value = 63;
return B_OK;
}
if (ch == '=') {
*value = 0;
return B_OK;
}
return B_BAD_DATA;
}
status_t
Base64DecodingDataIO::_ReadSingleByte(void* buffer)
{
uint8 delegateRead;
uint8 delegateReadInt;
status_t result = B_OK;
if (result == B_OK)
result = fDelegate->ReadExactly(&delegateRead, 1);
if (result == B_OK)
result = _CharToInt(delegateRead, &delegateReadInt);
if (result == B_OK && 0 == fNextByteAssemblyBits) {
fNextByteAssembly = delegateReadInt;
fNextByteAssemblyBits = 6;
return _ReadSingleByte(buffer);
}
if (result == B_OK) {
uint8 followingNextByteAssemblyBits = (6 - (8 - fNextByteAssemblyBits));
*((uint8 *) buffer) = fNextByteAssembly << (8 - fNextByteAssemblyBits)
| (delegateReadInt >> followingNextByteAssemblyBits);
fNextByteAssembly = delegateReadInt & (0x3f >> (6 - followingNextByteAssemblyBits));
fNextByteAssemblyBits = followingNextByteAssemblyBits;
}
return result;
}
ssize_t
Base64DecodingDataIO::Read(void* buffer, size_t size)
{
size_t readSize = 0;
status_t result = B_OK;
while (result == B_OK && readSize < size) {
result = _ReadSingleByte(&((uint8_t*) buffer)[readSize]);
if (result == B_OK)
readSize++;
}
return readSize++;
}
ssize_t
Base64DecodingDataIO::Write(const void* buffer, size_t size)
{
return B_NOT_SUPPORTED;
}
status_t
Base64DecodingDataIO::Flush()
{
return B_OK;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2018-2020, Andrew Lindesay <apl@lindesay.co.nz>.
* Copyright 2018-2023, Andrew Lindesay <apl@lindesay.co.nz>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
#ifndef DATA_IO_UTILS_H
@ -40,4 +40,29 @@ private:
};
class Base64DecodingDataIO : public BDataIO {
public:
Base64DecodingDataIO(BDataIO* delegate,
char char62 = '+', char char63 = '/');
virtual ~Base64DecodingDataIO();
virtual ssize_t Read(void* buffer, size_t size);
virtual ssize_t Write(const void* buffer, size_t size);
virtual status_t Flush();
private:
status_t _ReadSingleByte(void* buffer);
status_t _CharToInt(uint8 ch, uint8* value);
private:
BDataIO* fDelegate;
char fChar62;
char fChar63;
uint8 fNextByteAssembly;
uint8 fNextByteAssemblyBits;
};
#endif // DATA_IO_UTILS_H

View File

@ -1,5 +1,5 @@
/*
* Copyright 2010-2013 Haiku, Inc. All rights reserved.
* Copyright 2010-2023 Haiku, Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
@ -60,6 +60,7 @@ BHttpAuthentication::BHttpAuthentication(const BHttpAuthentication& other)
fAuthenticationMethod(other.fAuthenticationMethod),
fUserName(other.fUserName),
fPassword(other.fPassword),
fToken(other.fToken),
fRealm(other.fRealm),
fDigestNonce(other.fDigestNonce),
fDigestCnonce(other.fDigestCnonce),
@ -79,6 +80,7 @@ BHttpAuthentication& BHttpAuthentication::operator=(
fAuthenticationMethod = other.fAuthenticationMethod;
fUserName = other.fUserName;
fPassword = other.fPassword;
fToken = other.fToken;
fRealm = other.fRealm;
fDigestNonce = other.fDigestNonce;
fDigestCnonce = other.fDigestCnonce;
@ -113,6 +115,15 @@ BHttpAuthentication::SetPassword(const BString& password)
}
void
BHttpAuthentication::SetToken(const BString& token)
{
fLock.Lock();
fToken = token;
fLock.Unlock();
}
void
BHttpAuthentication::SetMethod(BHttpAuthenticationMethod method)
{
@ -151,7 +162,9 @@ BHttpAuthentication::Initialize(const BString& wwwAuthenticate)
else if (authRequired == "digest") {
fAuthenticationMethod = B_HTTP_AUTHENTICATION_DIGEST;
fDigestAlgorithm = B_HTTP_AUTHENTICATION_ALGORITHM_MD5;
} else
} else if (authRequired == "bearer")
fAuthenticationMethod = B_HTTP_AUTHENTICATION_BEARER;
else
return B_ERROR;
@ -206,6 +219,8 @@ BHttpAuthentication::Initialize(const BString& wwwAuthenticate)
if (fAuthenticationMethod == B_HTTP_AUTHENTICATION_BASIC)
return B_OK;
else if (fAuthenticationMethod == B_HTTP_AUTHENTICATION_BEARER)
return B_OK;
else if (fAuthenticationMethod == B_HTTP_AUTHENTICATION_DIGEST
&& fDigestNonce.Length() > 0
&& fDigestAlgorithm != B_HTTP_AUTHENTICATION_ALGORITHM_NONE) {
@ -260,6 +275,10 @@ BHttpAuthentication::Authorization(const BUrl& url, const BString& method) const
break;
}
case B_HTTP_AUTHENTICATION_BEARER:
authorizationString << "Bearer " << fToken;
break;
case B_HTTP_AUTHENTICATION_DIGEST:
case B_HTTP_AUTHENTICATION_IE_DIGEST:
authorizationString << "Digest " << "username=\"" << fUserName
@ -384,6 +403,7 @@ BHttpAuthentication::_DigestResponse(const BString& uri, const BString& method)
PRINT(("HttpAuth: Computing digest response: \n"));
PRINT(("HttpAuth: > username = %s\n", fUserName.String()));
PRINT(("HttpAuth: > password = %s\n", fPassword.String()));
PRINT(("HttpAuth: > token = %s\n", fToken.String()));
PRINT(("HttpAuth: > realm = %s\n", fRealm.String()));
PRINT(("HttpAuth: > nonce = %s\n", fDigestNonce.String()));
PRINT(("HttpAuth: > cnonce = %s\n", fDigestCnonce.String()));

View File

@ -0,0 +1,128 @@
/*
* Copyright 2023, Andrew Lindesay <apl@lindesay.co.nz>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
#include "DataIOUtilsTest.h"
#include <String.h>
#include <cppunit/TestCaller.h>
#include <cppunit/TestSuite.h>
#include <string.h>
#include "DataIOUtils.h"
DataIOUtilsTest::DataIOUtilsTest()
{
}
DataIOUtilsTest::~DataIOUtilsTest()
{
}
void
DataIOUtilsTest::TestReadBase64JwtClaims_1()
{
const char* jwtToken = "eyJpc3MiOiJkZXYuaGRzIiwic3ViIjoiZXJpazY0QGhkcyIs"
"ImV4cCI6MTY5MzE5MTMzMiwiaWF0IjoxNjkzMTkxMDMyfQ";
BMemoryIO memoryIo(jwtToken, strlen(jwtToken));
Base64DecodingDataIO base64DecodingIo(&memoryIo, '-', '_');
char actualOutputBuffer[71];
size_t actualReadBytes;
bzero(actualOutputBuffer, 71);
// ----------------------
status_t result = base64DecodingIo.ReadExactly(actualOutputBuffer, 70, &actualReadBytes);
// ----------------------
CPPUNIT_ASSERT_EQUAL(B_OK, result);
CPPUNIT_ASSERT_EQUAL(70, actualReadBytes);
actualOutputBuffer[actualReadBytes] = 0;
CPPUNIT_ASSERT_EQUAL(0x7b, (uint8) actualOutputBuffer[0]);
CPPUNIT_ASSERT_EQUAL(
BString("{\"iss\":\"dev.hds\",\"sub\":\"erik64@hds\",\"exp\":1693191332,\"iat\""
":1693191032}"),
BString(actualOutputBuffer));
}
void
DataIOUtilsTest::TestReadBase64JwtClaims_2()
{
const char* jwtToken = "eyJpc3MiOiJkZXYuaGRzIiwic3ViIjoidG93ZWxkb3dudGVhQ"
"GhkcyIsImV4cCI6MTY5MzczODgyNiwiaWF0IjoxNjkzNzM4NTI2fQ";
BMemoryIO memoryIo(jwtToken, strlen(jwtToken));
Base64DecodingDataIO base64DecodingIo(&memoryIo, '-', '_');
char actualOutputBuffer[77];
size_t actualReadBytes;
bzero(actualOutputBuffer, 77);
// ----------------------
status_t result = base64DecodingIo.ReadExactly(actualOutputBuffer, 76, &actualReadBytes);
// ----------------------
CPPUNIT_ASSERT_EQUAL(B_OK, result);
CPPUNIT_ASSERT_EQUAL(76, actualReadBytes);
actualOutputBuffer[actualReadBytes] = 0;
CPPUNIT_ASSERT_EQUAL(0x7b, (uint8) actualOutputBuffer[0]);
CPPUNIT_ASSERT_EQUAL(
BString("{\"iss\":\"dev.hds\",\"sub\":\"toweldowntea@hds\",\"exp\":1693738826,\"iat\""
":1693738526}"),
BString(actualOutputBuffer));
}
void
DataIOUtilsTest::TestCorrupt()
{
const char* jwtToken = "QW5k$mV3";
// note that '$' is not a valid base64 character
BMemoryIO memoryIo(jwtToken, strlen(jwtToken));
Base64DecodingDataIO base64DecodingIo(&memoryIo, '-', '_');
char actualOutputBuffer[7];
size_t actualReadBytes;
bzero(actualOutputBuffer, 7);
// ----------------------
status_t result = base64DecodingIo.ReadExactly(actualOutputBuffer, 6, &actualReadBytes);
// ----------------------
CPPUNIT_ASSERT(B_OK != result);
}
/*static*/ void
DataIOUtilsTest::AddTests(BTestSuite& parent)
{
CppUnit::TestSuite& suite = *new CppUnit::TestSuite("DataIOUtilsTest");
suite.addTest(
new CppUnit::TestCaller<DataIOUtilsTest>(
"DataIOUtilsTest::TestReadBase64JwtClaims_1",
&DataIOUtilsTest::TestReadBase64JwtClaims_1));
suite.addTest(
new CppUnit::TestCaller<DataIOUtilsTest>(
"DataIOUtilsTest::TestReadBase64JwtClaims_2",
&DataIOUtilsTest::TestReadBase64JwtClaims_2));
suite.addTest(
new CppUnit::TestCaller<DataIOUtilsTest>(
"DataIOUtilsTest::TestCorrupt",
&DataIOUtilsTest::TestCorrupt));
parent.addTest("DataIOUtilsTest", &suite);
}

View File

@ -0,0 +1,26 @@
/*
* Copyright 2023, Andrew Lindesay <apl@lindesay.co.nz>
* Distributed under the terms of the MIT License.
*/
#ifndef DATA_IO_UTILS_TEST_H
#define DATA_IO_UTILS_TEST_H
#include <TestCase.h>
#include <TestSuite.h>
class DataIOUtilsTest : public CppUnit::TestCase {
public:
DataIOUtilsTest();
virtual ~DataIOUtilsTest();
void TestReadBase64JwtClaims_1();
void TestReadBase64JwtClaims_2();
void TestCorrupt();
static void AddTests(BTestSuite& suite);
};
#endif // DATA_IO_UTILS_TEST_H

View File

@ -1,5 +1,5 @@
/*
* Copyright 2017-2020, Andrew Lindesay, apl@lindesay.co.nz
* Copyright 2017-2023, Andrew Lindesay, apl@lindesay.co.nz
* Distributed under the terms of the MIT License.
*/
@ -8,7 +8,9 @@
#include <TestSuiteAddon.h>
#include "StandardMetaDataJsonEventListenerTest.h"
#include "DataIOUtilsTest.h"
#include "DumpExportRepositoryJsonListenerTest.h"
#include "JwtTokenHelperTest.h"
#include "ValidationFailureTest.h"
#include "ValidationUtilsTest.h"
#include "StorageUtilsTest.h"
@ -21,7 +23,10 @@ getTestSuite()
BTestSuite* suite = new BTestSuite("HaikuDepot");
StandardMetaDataJsonEventListenerTest::AddTests(*suite);
DataIOUtilsTest::AddTests(*suite);
DumpExportRepositoryJsonListenerTest::AddTests(*suite);
DumpExportRepositoryJsonListenerTest::AddTests(*suite);
JwtTokenHelperTest::AddTests(*suite);
ValidationFailureTest::AddTests(*suite);
ValidationUtilsTest::AddTests(*suite);
StorageUtilsTest::AddTests(*suite);

View File

@ -33,6 +33,7 @@ UnitTestLib haikudepottest.so :
HaikuDepotTestAddon.cpp
DataIOUtils.cpp
DataIOUtilsTest.cpp
DumpExportRepositorySource.cpp
DumpExportRepositorySourceMirror.cpp
@ -40,6 +41,9 @@ UnitTestLib haikudepottest.so :
DumpExportRepositoryJsonListener.cpp
DumpExportRepositoryJsonListenerTest.cpp
JwtTokenHelper.cpp
JwtTokenHelperTest.cpp
Logger.cpp
StandardMetaData.cpp

View File

@ -0,0 +1,102 @@
/*
* Copyright 2023, Andrew Lindesay <apl@lindesay.co.nz>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
#include "JwtTokenHelperTest.h"
#include <String.h>
#include <cppunit/TestCaller.h>
#include <cppunit/TestSuite.h>
#include <string.h>
#include "JwtTokenHelper.h"
JwtTokenHelperTest::JwtTokenHelperTest()
{
}
JwtTokenHelperTest::~JwtTokenHelperTest()
{
}
void
JwtTokenHelperTest::TestParseTokenClaims()
{
const char* jwtToken = "eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJkZXYuaGRzIiwic3ViIj"
"oiZXJpazY0QGhkcyIsImV4cCI6MTY5MzkwNzM1NywiaWF0IjoxNjkzOTA3MDU3fQ.DJOz0"
"TmgN0Ya8De-oV0mBwWb-8FYavLbaFUFhCLqr-s";
BMessage actualMessage;
// ----------------------
status_t result = JwtTokenHelper::ParseClaims(BString(jwtToken), actualMessage);
// ----------------------
CPPUNIT_ASSERT_EQUAL(B_OK, result);
_AssertStringValue(actualMessage, "iss", "dev.hds");
_AssertStringValue(actualMessage, "sub", "erik64@hds");
_AssertDoubleValue(actualMessage, "exp", 1693907357);
_AssertDoubleValue(actualMessage, "iat", 1693907057);
}
void
JwtTokenHelperTest::TestCorrupt()
{
const char* jwtToken = "application/json";
BMessage actualMessage;
// ----------------------
status_t result = JwtTokenHelper::ParseClaims(BString(jwtToken), actualMessage);
// ----------------------
CPPUNIT_ASSERT(B_OK != result);
}
/*static*/ void
JwtTokenHelperTest::AddTests(BTestSuite& parent)
{
CppUnit::TestSuite& suite = *new CppUnit::TestSuite("JwtTokenHelperTest");
suite.addTest(
new CppUnit::TestCaller<JwtTokenHelperTest>(
"JwtTokenHelperTest::TestParseTokenClaims",
&JwtTokenHelperTest::TestParseTokenClaims));
suite.addTest(
new CppUnit::TestCaller<JwtTokenHelperTest>(
"JwtTokenHelperTest::TestCorrupt",
&JwtTokenHelperTest::TestCorrupt));
parent.addTest("JwtTokenHelperTest", &suite);
}
void
JwtTokenHelperTest::_AssertStringValue(const BMessage& message, const char* key,
const char* expectedValue) const
{
BString value;
status_t result = message.FindString(key, &value);
CPPUNIT_ASSERT_EQUAL(B_OK, result);
CPPUNIT_ASSERT_EQUAL(BString(expectedValue), value);
}
void
JwtTokenHelperTest::_AssertDoubleValue(const BMessage& message, const char* key,
int64 expectedValue) const
{
double value;
status_t result = message.FindDouble(key, &value);
CPPUNIT_ASSERT_EQUAL(B_OK, result);
CPPUNIT_ASSERT_EQUAL((double) expectedValue, value);
}

View File

@ -0,0 +1,33 @@
/*
* Copyright 2023, Andrew Lindesay <apl@lindesay.co.nz>
* Distributed under the terms of the MIT License.
*/
#ifndef JWT_TOKEN_HELPER_TEST_H
#define JWT_TOKEN_HELPER_TEST_H
#include <Message.h>
#include <TestCase.h>
#include <TestSuite.h>
class JwtTokenHelperTest : public CppUnit::TestCase {
public:
JwtTokenHelperTest();
virtual ~JwtTokenHelperTest();
void TestParseTokenClaims();
void TestCorrupt();
static void AddTests(BTestSuite& suite);
private:
void _AssertStringValue(const BMessage& message, const char* key,
const char* expectedValue) const;
void _AssertDoubleValue(const BMessage& message, const char* key,
int64 expectedValue) const;
};
#endif // JWT_TOKEN_HELPER_TEST_H