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 <[email protected]>
Reviewed-by: Jérôme Duval <[email protected]>
This commit is contained in:
Andrew Lindesay
2023-09-25 06:20:32 +00:00
parent d8bca3564a
commit 4b347fccb2
31 changed files with 1103 additions and 160 deletions
@@ -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. * Distributed under the terms of the MIT License.
*/ */
#ifndef _B_HTTP_AUTHENTICATION_H_ #ifndef _B_HTTP_AUTHENTICATION_H_
@@ -24,8 +24,10 @@ enum BHttpAuthenticationMethod {
// Basic base64 authentication method (unsecure) // Basic base64 authentication method (unsecure)
B_HTTP_AUTHENTICATION_DIGEST = 2, B_HTTP_AUTHENTICATION_DIGEST = 2,
// Digest authentication // Digest authentication
B_HTTP_AUTHENTICATION_IE_DIGEST = 4 B_HTTP_AUTHENTICATION_IE_DIGEST = 4,
// Slightly modified digest authentication to mimic old IE one // 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 // Field modification
void SetUserName(const BString& username); void SetUserName(const BString& username);
void SetPassword(const BString& password); void SetPassword(const BString& password);
void SetToken(const BString& token);
void SetMethod( void SetMethod(
BHttpAuthenticationMethod type); BHttpAuthenticationMethod type);
status_t Initialize(const BString& wwwAuthenticate); status_t Initialize(const BString& wwwAuthenticate);
@@ -63,6 +66,7 @@ public:
// Field access // Field access
const BString& UserName() const; const BString& UserName() const;
const BString& Password() const; const BString& Password() const;
const BString& Token() const;
BHttpAuthenticationMethod Method() const; BHttpAuthenticationMethod Method() const;
BString Authorization(const BUrl& url, BString Authorization(const BUrl& url,
@@ -89,6 +93,7 @@ private:
BHttpAuthenticationMethod fAuthenticationMethod; BHttpAuthenticationMethod fAuthenticationMethod;
BString fUserName; BString fUserName;
BString fPassword; BString fPassword;
BString fToken;
BString fRealm; BString fRealm;
BString fDigestNonce; BString fDigestNonce;
+1 -1
View File
@@ -8,7 +8,7 @@ resource app_flags B_SINGLE_LAUNCH;
resource app_version { resource app_version {
major = 0, major = 0,
middle = 0, middle = 0,
minor = 6, minor = 7,
variety = B_APPV_ALPHA, variety = B_APPV_ALPHA,
internal = 1, internal = 1,
+2 -1
View File
@@ -1,5 +1,5 @@
/* /*
* Copyright 2018-2021, Andrew Lindesay <[email protected]>. * Copyright 2018-2023, Andrew Lindesay <[email protected]>.
* All rights reserved. Distributed under the terms of the MIT License. * All rights reserved. Distributed under the terms of the MIT License.
*/ */
#ifndef HAIKU_DEPOT_CONSTANTS_H #ifndef HAIKU_DEPOT_CONSTANTS_H
@@ -66,6 +66,7 @@ enum BitmapSize {
#define HD_CLIENT_TOO_OLD (HD_ERROR_BASE + 2) #define HD_CLIENT_TOO_OLD (HD_ERROR_BASE + 2)
#define HD_ERR_NOT_MODIFIED (HD_ERROR_BASE + 3) #define HD_ERR_NOT_MODIFIED (HD_ERROR_BASE + 3)
#define HD_ERR_NO_DATA (HD_ERROR_BASE + 4) #define HD_ERR_NO_DATA (HD_ERROR_BASE + 4)
#define HD_AUTHENTICATION_FAILED (HD_ERROR_BASE + 5)
#define REPOSITORY_NAME_SYSTEM "system" #define REPOSITORY_NAME_SYSTEM "system"
+2
View File
@@ -113,6 +113,7 @@ local textDocumentSources =
local applicationSources = local applicationSources =
App.cpp App.cpp
AccessToken.cpp
BitmapView.cpp BitmapView.cpp
Captcha.cpp Captcha.cpp
CreateUserDetail.cpp CreateUserDetail.cpp
@@ -124,6 +125,7 @@ local applicationSources =
IconTarPtr.cpp IconTarPtr.cpp
IncrementViewCounterProcess.cpp IncrementViewCounterProcess.cpp
JobStateListener.cpp JobStateListener.cpp
JwtTokenHelper.cpp
LanguageModel.cpp LanguageModel.cpp
LinkView.cpp LinkView.cpp
LinkedBitmapView.cpp LinkedBitmapView.cpp
+134
View File
@@ -0,0 +1,134 @@
/*
* Copyright 2023, Andrew Lindesay <[email protected]>.
* 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;
}
+51
View File
@@ -0,0 +1,51 @@
/*
* Copyright 2023, Andrew Lindesay <[email protected]>.
* 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
+6 -6
View File
@@ -1,7 +1,7 @@
/* /*
* Copyright 2013-2014, Stephan Aßmus <[email protected]>. * Copyright 2013-2014, Stephan Aßmus <[email protected]>.
* Copyright 2014, Axel Dörfler <[email protected]>. * Copyright 2014, Axel Dörfler <[email protected]>.
* Copyright 2016-2022, Andrew Lindesay <[email protected]>. * Copyright 2016-2023, Andrew Lindesay <[email protected]>.
* All rights reserved. Distributed under the terms of the MIT License. * 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 status_t status = fWebAppInterface
.RetreiveUserRatingsForPackageForDisplay(packageName, .RetrieveUserRatingsForPackageForDisplay(packageName,
webAppRepositoryCode, webAppRepositorySourceCode, 0, webAppRepositoryCode, webAppRepositorySourceCode, 0,
PACKAGE_INFO_MAX_USER_RATINGS, info); PACKAGE_INFO_MAX_USER_RATINGS, info);
if (status == B_OK) { if (status == B_OK) {
@@ -780,19 +780,19 @@ Model::SetNickname(BString nickname)
nickname = ""; nickname = "";
} }
SetAuthorization(nickname, password, false); SetCredentials(nickname, password, false);
} }
const BString& const BString&
Model::Nickname() const Model::Nickname()
{ {
return fWebAppInterface.Nickname(); return fWebAppInterface.Nickname();
} }
void void
Model::SetAuthorization(const BString& nickname, const BString& passwordClear, Model::SetCredentials(const BString& nickname, const BString& passwordClear,
bool storePassword) bool storePassword)
{ {
BString existingNickname = Nickname(); BString existingNickname = Nickname();
@@ -820,7 +820,7 @@ Model::SetAuthorization(const BString& nickname, const BString& passwordClear,
} }
BAutolock locker(&fLock); BAutolock locker(&fLock);
fWebAppInterface.SetAuthorization(UserCredentials(nickname, passwordClear)); fWebAppInterface.SetCredentials(UserCredentials(nickname, passwordClear));
if (nickname != existingNickname) if (nickname != existingNickname)
_NotifyAuthorizationChanged(); _NotifyAuthorizationChanged();
+5 -6
View File
@@ -1,6 +1,6 @@
/* /*
* Copyright 2013-2014, Stephan Aßmus <[email protected]>. * Copyright 2013-2014, Stephan Aßmus <[email protected]>.
* Copyright 2016-2021, Andrew Lindesay <[email protected]>. * Copyright 2016-2023, Andrew Lindesay <[email protected]>.
* All rights reserved. Distributed under the terms of the MIT License. * All rights reserved. Distributed under the terms of the MIT License.
*/ */
#ifndef MODEL_H #ifndef MODEL_H
@@ -148,14 +148,13 @@ public:
uint32 flags); uint32 flags);
void SetNickname(BString nickname); void SetNickname(BString nickname);
const BString& Nickname() const; const BString& Nickname();
void SetAuthorization(const BString& nickname, void SetCredentials(const BString& nickname,
const BString& passwordClear, const BString& passwordClear,
bool storePassword); bool storePassword);
WebAppInterface& WebAppInterface* GetWebAppInterface()
GetWebAppInterface() { return &fWebAppInterface; }
{ return fWebAppInterface; }
status_t IconTarPath(BPath& path) const; status_t IconTarPath(BPath& path) const;
status_t DumpExportReferenceDataPath(BPath& path); status_t DumpExportReferenceDataPath(BPath& path);
+25 -10
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& const BString&
UserCredentials::Nickname() const 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 status_t
UserCredentials::Archive(BMessage* into, bool deep) const UserCredentials::Archive(BMessage* into, bool deep) const
{ {
+4 -2
View File
@@ -25,6 +25,10 @@ public:
UserCredentials(); UserCredentials();
virtual ~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& Nickname() const;
const BString& PasswordClear() const; const BString& PasswordClear() const;
const bool IsSuccessful() const; const bool IsSuccessful() const;
@@ -34,8 +38,6 @@ public:
void SetPasswordClear(const BString& value); void SetPasswordClear(const BString& value);
void SetIsSuccessful(bool value); void SetIsSuccessful(bool value);
UserCredentials& operator=(const UserCredentials& other);
status_t Archive(BMessage* into, bool deep = true) const; status_t Archive(BMessage* into, bool deep = true) const;
private: private:
BString fNickname; BString fNickname;
@@ -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. * All rights reserved. Distributed under the terms of the MIT License.
*/ */
#include "IncrementViewCounterProcess.h" #include "IncrementViewCounterProcess.h"
@@ -81,13 +81,11 @@ IncrementViewCounterProcess::RunInternal()
while (attempts > 0 && !WasStopped()) { while (attempts > 0 && !WasStopped()) {
BMessage resultEnvelope; BMessage resultEnvelope;
WebAppInterface& webAppInterface = fModel->GetWebAppInterface(); WebAppInterface* webAppInterface = fModel->GetWebAppInterface();
result = webAppInterface.IncrementViewCounter(fPackage, depot, result = webAppInterface->IncrementViewCounter(fPackage, depot, resultEnvelope);
resultEnvelope);
if (result == B_OK) { if (result == B_OK) {
int32 errorCode = webAppInterface.ErrorCodeFromResponse( int32 errorCode = WebAppInterface::ErrorCodeFromResponse(resultEnvelope);
resultEnvelope);
switch (errorCode) { switch (errorCode) {
case ERROR_CODE_NONE: case ERROR_CODE_NONE:
HDINFO("did increment the view counter for [%s]", HDINFO("did increment the view counter for [%s]",
@@ -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;
}
@@ -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
@@ -99,18 +99,18 @@ UserDetailVerifierProcess::_ShouldVerify()
status_t status_t
UserDetailVerifierProcess::_TryFetchUserDetail(UserDetail& userDetail) UserDetailVerifierProcess::_TryFetchUserDetail(UserDetail& userDetail)
{ {
WebAppInterface interface = fModel->GetWebAppInterface(); WebAppInterface* interface = fModel->GetWebAppInterface();
BMessage userDetailResponse; BMessage userDetailResponse;
status_t result; status_t result;
result = interface.RetrieveCurrentUserDetail(userDetailResponse); result = interface->RetrieveCurrentUserDetail(userDetailResponse);
if (result != B_OK) { if (result != B_OK) {
HDERROR("a problem has arisen retrieving the current user detail: %s", HDERROR("a problem has arisen retrieving the current user detail: %s",
strerror(result)); strerror(result));
} }
if (result == B_OK) { if (result == B_OK) {
int32 errorCode = interface.ErrorCodeFromResponse(userDetailResponse); int32 errorCode = WebAppInterface::ErrorCodeFromResponse(userDetailResponse);
switch (errorCode) { switch (errorCode) {
case ERROR_CODE_NONE: case ERROR_CODE_NONE:
break; break;
@@ -132,7 +132,7 @@ UserDetailVerifierProcess::_TryFetchUserDetail(UserDetail& userDetail)
// worked, it is now necessary to check to see that the user has agreed // worked, it is now necessary to check to see that the user has agreed
// to the most recent user-usage conditions. // to the most recent user-usage conditions.
result = interface.UnpackUserDetail(userDetailResponse, userDetail); result = interface->UnpackUserDetail(userDetailResponse, userDetail);
if (result != B_OK) if (result != B_OK)
HDERROR("it was not possible to unpack the user details."); HDERROR("it was not possible to unpack the user details.");
} }
+197 -46
View File
@@ -6,21 +6,24 @@
#include "WebAppInterface.h" #include "WebAppInterface.h"
#include <AutoDeleter.h>
#include <Application.h> #include <Application.h>
#include <Message.h>
#include <Url.h>
#include <AutoDeleter.h>
#include <AutoLocker.h>
#include <HttpHeaders.h> #include <HttpHeaders.h>
#include <HttpRequest.h> #include <HttpRequest.h>
#include <Json.h> #include <Json.h>
#include <JsonTextWriter.h> #include <JsonTextWriter.h>
#include <JsonMessageWriter.h> #include <JsonMessageWriter.h>
#include <Message.h>
#include <Url.h>
#include <UrlContext.h> #include <UrlContext.h>
#include <UrlProtocolListener.h> #include <UrlProtocolListener.h>
#include <UrlProtocolRoster.h> #include <UrlProtocolRoster.h>
#include "DataIOUtils.h" #include "DataIOUtils.h"
#include "HaikuDepotConstants.h" #include "HaikuDepotConstants.h"
#include "JwtTokenHelper.h"
#include "Logger.h" #include "Logger.h"
#include "ServerSettings.h" #include "ServerSettings.h"
#include "ServerHelper.h" #include "ServerHelper.h"
@@ -103,10 +106,6 @@ make_http_request(const BUrl& url, BDataIO* output,
} }
int
WebAppInterface::fRequestIndex = 0;
enum { enum {
NEEDS_AUTHORIZATION = 1 << 0, NEEDS_AUTHORIZATION = 1 << 0,
}; };
@@ -117,38 +116,26 @@ WebAppInterface::WebAppInterface()
} }
WebAppInterface::WebAppInterface(const WebAppInterface& other)
:
fCredentials(other.fCredentials)
{
}
WebAppInterface::~WebAppInterface() WebAppInterface::~WebAppInterface()
{ {
} }
WebAppInterface&
WebAppInterface::operator=(const WebAppInterface& other)
{
if (this == &other)
return *this;
fCredentials = other.fCredentials;
return *this;
}
void void
WebAppInterface::SetAuthorization(const UserCredentials& value) WebAppInterface::SetCredentials(const UserCredentials& value)
{ {
AutoLocker<BLocker> lock(&fLock);
if (fCredentials != value) {
fCredentials = value; fCredentials = value;
fAccessToken.Clear();
}
} }
const BString& const BString&
WebAppInterface::Nickname() const WebAppInterface::Nickname()
{ {
AutoLocker<BLocker> lock(&fLock);
return fCredentials.Nickname(); return fCredentials.Nickname();
} }
@@ -172,7 +159,7 @@ WebAppInterface::GetChangelog(const BString& packageName, BMessage& message)
status_t status_t
WebAppInterface::RetreiveUserRatingsForPackageForDisplay( WebAppInterface::RetrieveUserRatingsForPackageForDisplay(
const BString& packageName, const BString& packageName,
const BString& webAppRepositoryCode, const BString& webAppRepositoryCode,
const BString& webAppRepositorySourceCode, const BString& webAppRepositorySourceCode,
@@ -209,7 +196,7 @@ WebAppInterface::RetreiveUserRatingsForPackageForDisplay(
status_t status_t
WebAppInterface::RetreiveUserRatingForPackageAndVersionByUser( WebAppInterface::RetrieveUserRatingForPackageAndVersionByUser(
const BString& packageName, const BPackageVersion& version, const BString& packageName, const BPackageVersion& version,
const BString& architecture, const BString& architecture,
const BString& webAppRepositoryCode, const BString& webAppRepositoryCode,
@@ -283,6 +270,26 @@ WebAppInterface::RetrieveUserDetailForCredentials(
"to obtain the user detail"); "to obtain the user detail");
} }
status_t result = B_OK;
// authenticate the user and obtain a token to use with the latter
// request.
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. // BHttpRequest later takes ownership of this.
BMallocIO* requestEnvelopeData = new BMallocIO(); BMallocIO* requestEnvelopeData = new BMallocIO();
BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData); BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData);
@@ -292,10 +299,11 @@ WebAppInterface::RetrieveUserDetailForCredentials(
requestEnvelopeWriter.WriteString(credentials.Nickname().String()); requestEnvelopeWriter.WriteString(credentials.Nickname().String());
requestEnvelopeWriter.WriteObjectEnd(); requestEnvelopeWriter.WriteObjectEnd();
status_t result = _SendJsonRequest("user/get-user", credentials, result = _SendJsonRequest("user/get-user", accessToken,
requestEnvelopeData, _LengthAndSeekToZero(requestEnvelopeData), requestEnvelopeData, _LengthAndSeekToZero(requestEnvelopeData),
NEEDS_AUTHORIZATION, message); NEEDS_AUTHORIZATION, message);
// note that the credentials used here are passed in as args. // note that the credentials used here are passed in as args.
}
return result; return result;
} }
@@ -308,7 +316,8 @@ WebAppInterface::RetrieveUserDetailForCredentials(
status_t status_t
WebAppInterface::RetrieveCurrentUserDetail(BMessage& message) 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 /*! \brief Returns data relating to the user usage conditions
\param code defines the version of the data to return or if empty then the \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.WriteObjectName("userUsageConditionsCode");
requestEnvelopeWriter.WriteString(code.String()); requestEnvelopeWriter.WriteString(code.String());
requestEnvelopeWriter.WriteObjectName("nickname"); requestEnvelopeWriter.WriteObjectName("nickname");
requestEnvelopeWriter.WriteString(fCredentials.Nickname()); requestEnvelopeWriter.WriteString(Nickname());
requestEnvelopeWriter.WriteObjectEnd(); requestEnvelopeWriter.WriteObjectEnd();
// now fetch this information into an object. // now fetch this information into an object.
@@ -504,7 +573,7 @@ WebAppInterface::CreateUserRating(const BString& packageName,
requestEnvelopeWriter.WriteObjectName("pkgVersionType"); requestEnvelopeWriter.WriteObjectName("pkgVersionType");
requestEnvelopeWriter.WriteString("SPECIFIC"); requestEnvelopeWriter.WriteString("SPECIFIC");
requestEnvelopeWriter.WriteObjectName("userNickname"); requestEnvelopeWriter.WriteObjectName("userNickname");
requestEnvelopeWriter.WriteString(fCredentials.Nickname()); requestEnvelopeWriter.WriteString(Nickname());
if (!version.Major().IsEmpty()) { if (!version.Major().IsEmpty()) {
requestEnvelopeWriter.WriteObjectName("pkgVersionMajor"); 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 status_t
WebAppInterface::AuthenticateUser(const BString& nickName, WebAppInterface::AuthenticateUser(const BString& nickName,
const BString& passwordClear, BMessage& message) const BString& passwordClear, BMessage& message)
@@ -800,7 +910,7 @@ WebAppInterface::_RetrievePasswordRequirementsMeta(BMessage& message)
not look like an error. not look like an error.
*/ */
int32 /*static*/ int32
WebAppInterface::ErrorCodeFromResponse(BMessage& responseEnvelopeMessage) WebAppInterface::ErrorCodeFromResponse(BMessage& responseEnvelopeMessage)
{ {
BMessage error; BMessage error;
@@ -821,17 +931,23 @@ WebAppInterface::ErrorCodeFromResponse(BMessage& responseEnvelopeMessage)
status_t status_t
WebAppInterface::_SendJsonRequest(const char* urlPathComponents, WebAppInterface::_SendJsonRequest(const char* urlPathComponents,
BPositionIO* requestData, size_t requestDataSize, uint32 flags, 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); requestDataSize, flags, reply);
} }
status_t /*static*/ status_t
WebAppInterface::_SendJsonRequest(const char* urlPathComponents, WebAppInterface::_SendJsonRequest(const char* urlPathComponents,
UserCredentials credentials, BPositionIO* requestData, const AccessToken& accessToken, BPositionIO* requestData,
size_t requestDataSize, uint32 flags, BMessage& reply) const size_t requestDataSize, uint32 flags, BMessage& reply)
{ {
if (requestDataSize == 0) { if (requestDataSize == 0) {
HDINFO("%s; empty request payload", PROTOCOL_NAME); HDINFO("%s; empty request payload", PROTOCOL_NAME);
@@ -852,6 +968,15 @@ WebAppInterface::_SendJsonRequest(const char* urlPathComponents,
return HD_CLIENT_TOO_OLD; 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/") BUrl url = ServerSettings::CreateFullUrl(BString("/__api/v2/")
<< urlPathComponents); << urlPathComponents);
HDDEBUG("%s; will make request to [%s]", PROTOCOL_NAME, HDDEBUG("%s; will make request to [%s]", PROTOCOL_NAME,
@@ -883,13 +1008,10 @@ WebAppInterface::_SendJsonRequest(const char* urlPathComponents,
request->SetMethod(B_HTTP_POST); request->SetMethod(B_HTTP_POST);
request->SetHeaders(headers); request->SetHeaders(headers);
// Authentication via Basic Authentication if (needsAuthorization) {
// The other way would be to obtain a token and then use the Token Bearer BHttpAuthentication authentication;
// header. authentication.SetMethod(B_HTTP_AUTHENTICATION_BEARER);
if (((flags & NEEDS_AUTHORIZATION) != 0) && credentials.IsValid()) { authentication.SetToken(accessToken.Token());
BHttpAuthentication authentication(credentials.Nickname(),
credentials.PasswordClear());
authentication.SetMethod(B_HTTP_AUTHENTICATION_BASIC);
context.AddAuthentication(url, authentication); context.AddAuthentication(url, authentication);
} }
@@ -948,7 +1070,7 @@ WebAppInterface::_SendJsonRequest(const char* urlPathComponents,
status_t status_t
WebAppInterface::_SendJsonRequest(const char* urlPathComponents, 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. // gets 'adopted' by the subsequent http request.
BMemoryIO* data = new BMemoryIO(jsonString.String(), BMemoryIO* data = new BMemoryIO(jsonString.String(),
@@ -1042,3 +1164,32 @@ WebAppInterface::_LengthAndSeekToZero(BPositionIO* data)
data->Seek(0, SEEK_SET); data->Seek(0, SEEK_SET);
return dataSize; 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;
}
+27 -15
View File
@@ -9,9 +9,11 @@
#include <Application.h> #include <Application.h>
#include <JsonWriter.h> #include <JsonWriter.h>
#include <Locker.h>
#include <String.h> #include <String.h>
#include <package/PackageVersion.h> #include <package/PackageVersion.h>
#include "AccessToken.h"
#include "PackageInfo.h" #include "PackageInfo.h"
#include "PasswordRequirements.h" #include "PasswordRequirements.h"
#include "UserCredentials.h" #include "UserCredentials.h"
@@ -43,26 +45,23 @@ using BPackageKit::BPackageVersion;
class WebAppInterface { class WebAppInterface {
public: public:
WebAppInterface(); WebAppInterface();
WebAppInterface(const WebAppInterface& other);
virtual ~WebAppInterface(); virtual ~WebAppInterface();
WebAppInterface& operator=(const WebAppInterface& other); void SetCredentials(const UserCredentials& value);
const BString& Nickname();
void SetAuthorization(const UserCredentials& value);
const BString& Nickname() const;
status_t GetChangelog( status_t GetChangelog(
const BString& packageName, const BString& packageName,
BMessage& message); BMessage& message);
status_t RetreiveUserRatingsForPackageForDisplay( status_t RetrieveUserRatingsForPackageForDisplay(
const BString& packageName, const BString& packageName,
const BString& webAppRepositoryCode, const BString& webAppRepositoryCode,
const BString& webAppRepositorySourceCode, const BString& webAppRepositorySourceCode,
int resultOffset, int maxResults, int resultOffset, int maxResults,
BMessage& message); BMessage& message);
status_t RetreiveUserRatingForPackageAndVersionByUser( status_t RetrieveUserRatingForPackageAndVersionByUser(
const BString& packageName, const BString& packageName,
const BPackageVersion& version, const BPackageVersion& version,
const BString& architecture, const BString& architecture,
@@ -121,6 +120,8 @@ public:
const BString& userUsageConditionsCode, const BString& userUsageConditionsCode,
BMessage& message); BMessage& message);
status_t AuthenticateUserRetainingAccessToken();
status_t AuthenticateUser(const BString& nickName, status_t AuthenticateUser(const BString& nickName,
const BString& passwordClear, const BString& passwordClear,
BMessage& message); BMessage& message);
@@ -139,7 +140,17 @@ public:
static status_t UnpackUserDetail( static status_t UnpackUserDetail(
BMessage& responseEnvelopeMessage, BMessage& responseEnvelopeMessage,
UserDetail& userDetail); UserDetail& userDetail);
static status_t UnpackAccessToken(
BMessage& responseEnvelopeMessage,
AccessToken& accessToken);
private: private:
UserCredentials _Credentials();
AccessToken _ObtainValidAccessToken();
status_t _AuthenticateUserRetainingAccessToken(const BString& nickName,
const BString& passwordClear);
status_t _RetrievePasswordRequirementsMeta( status_t _RetrievePasswordRequirementsMeta(
BMessage& message); BMessage& message);
@@ -151,16 +162,16 @@ private:
status_t _SendJsonRequest(const char* urlPathComponents, status_t _SendJsonRequest(const char* urlPathComponents,
const BString& jsonString, uint32 flags, const BString& jsonString, uint32 flags,
BMessage& reply) const; BMessage& reply);
status_t _SendJsonRequest(const char* urlPathComponents,
UserCredentials credentials,
BPositionIO* requestData,
size_t requestDataSize, uint32 flags,
BMessage& reply) const;
status_t _SendJsonRequest(const char* urlPathComponents, status_t _SendJsonRequest(const char* urlPathComponents,
BPositionIO* requestData, BPositionIO* requestData,
size_t requestDataSize, uint32 flags, 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( status_t _SendRawGetRequest(
const BString urlPathComponents, const BString urlPathComponents,
@@ -171,7 +182,8 @@ private:
private: private:
UserCredentials fCredentials; UserCredentials fCredentials;
static int fRequestIndex; AccessToken fAccessToken;
BLocker fLock;
}; };
+2 -2
View File
@@ -3,7 +3,7 @@
* Copyright 2013-2014, Stephan Aßmus <superstippi@gmx.de>. * Copyright 2013-2014, Stephan Aßmus <superstippi@gmx.de>.
* Copyright 2013, Rene Gollent, rene@gollent.com. * Copyright 2013, Rene Gollent, rene@gollent.com.
* Copyright 2013, Ingo Weinhold, ingo_weinhold@gmx.de. * 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>. * Copyright 2017, Julian Harnath <julian.harnath@rwth-aachen.de>.
* All rights reserved. Distributed under the terms of the MIT License. * 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 void
MainWindow::StoreSettings(BMessage& settings) const MainWindow::StoreSettings(BMessage& settings)
{ {
settings.AddRect(_WindowFrameName(), Frame()); settings.AddRect(_WindowFrameName(), Frame());
if (!fSinglePackageMode) { if (!fSinglePackageMode) {
+2 -2
View File
@@ -2,7 +2,7 @@
* Copyright 2013-2014, Stephan Aßmus <superstippi@gmx.de>. * Copyright 2013-2014, Stephan Aßmus <superstippi@gmx.de>.
* Copyright 2013, Rene Gollent <rene@gollent.com>. * Copyright 2013, Rene Gollent <rene@gollent.com>.
* Copyright 2017, Julian Harnath <julian.harnath@rwth-aachen.de>. * 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. * All rights reserved. Distributed under the terms of the MIT License.
*/ */
#ifndef MAIN_WINDOW_H #ifndef MAIN_WINDOW_H
@@ -48,7 +48,7 @@ public:
virtual bool QuitRequested(); virtual bool QuitRequested();
virtual void MessageReceived(BMessage* message); virtual void MessageReceived(BMessage* message);
void StoreSettings(BMessage& message) const; void StoreSettings(BMessage& message);
// ProcessCoordinatorConsumer // ProcessCoordinatorConsumer
virtual void Consume(ProcessCoordinator *item); virtual void Consume(ProcessCoordinator *item);
+10 -10
View File
@@ -1,6 +1,6 @@
/* /*
* Copyright 2014, Stephan Aßmus <superstippi@gmx.de>. * 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. * All rights reserved. Distributed under the terms of the MIT License.
*/ */
@@ -544,7 +544,8 @@ RatePackageWindow::_QueryRatingThread()
return; return;
} }
WebAppInterface interface; WebAppInterface* interface = fModel.GetWebAppInterface();
BMessage info; BMessage info;
const DepotInfo* depot = fModel.DepotForName(package->DepotName()); const DepotInfo* depot = fModel.DepotForName(package->DepotName());
BString webAppRepositoryCode; BString webAppRepositoryCode;
@@ -561,16 +562,15 @@ RatePackageWindow::_QueryRatingThread()
"code for depot; %s", package->DepotName().String()); "code for depot; %s", package->DepotName().String());
BMessenger(this).SendMessage(B_QUIT_REQUESTED); BMessenger(this).SendMessage(B_QUIT_REQUESTED);
} else { } else {
status_t status = interface status_t status = interface->RetrieveUserRatingForPackageAndVersionByUser(
.RetreiveUserRatingForPackageAndVersionByUser(package->Name(), package->Name(), package->Version(), package->Architecture(),
package->Version(), package->Architecture(),
webAppRepositoryCode, webAppRepositorySourceCode, webAppRepositoryCode, webAppRepositorySourceCode,
nickname, info); nickname, info);
if (status == B_OK) { if (status == B_OK) {
// could be an error or could be a valid response envelope // could be an error or could be a valid response envelope
// containing data. // containing data.
switch (interface.ErrorCodeFromResponse(info)) { switch (WebAppInterface::ErrorCodeFromResponse(info)) {
case ERROR_CODE_NONE: case ERROR_CODE_NONE:
{ {
//info.PrintToStream(); //info.PrintToStream();
@@ -647,7 +647,7 @@ RatePackageWindow::_SendRatingThread()
webAppRepositorySourceCode = depot->WebAppRepositorySourceCode(); webAppRepositorySourceCode = depot->WebAppRepositorySourceCode();
} }
WebAppInterface interface = fModel.GetWebAppInterface(); WebAppInterface* interface = fModel.GetWebAppInterface();
Unlock(); Unlock();
@@ -672,11 +672,11 @@ RatePackageWindow::_SendRatingThread()
BMessage info; BMessage info;
if (ratingID.Length() > 0) { if (ratingID.Length() > 0) {
HDINFO("will update the existing user rating [%s]", ratingID.String()); HDINFO("will update the existing user rating [%s]", ratingID.String());
status = interface.UpdateUserRating(ratingID, status = interface->UpdateUserRating(ratingID,
languageCode, comment, stability, rating, active, info); languageCode, comment, stability, rating, active, info);
} else { } else {
HDINFO("will create a new user rating for pkg [%s]", package.String()); 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, architecture, webAppRepositoryCode, webAppRepositorySourceCode,
languageCode, comment, stability, rating, info); languageCode, comment, stability, rating, info);
} }
@@ -684,7 +684,7 @@ RatePackageWindow::_SendRatingThread()
if (status == B_OK) { if (status == B_OK) {
// could be an error or could be a valid response envelope // could be an error or could be a valid response envelope
// containing data. // containing data.
switch (interface.ErrorCodeFromResponse(info)) { switch (WebAppInterface::ErrorCodeFromResponse(info)) {
case ERROR_CODE_NONE: case ERROR_CODE_NONE:
{ {
if (ratingID.Length() > 0) if (ratingID.Length() > 0)
@@ -293,9 +293,9 @@ void
ToLatestUserUsageConditionsWindow::_FetchDataPerform() ToLatestUserUsageConditionsWindow::_FetchDataPerform()
{ {
UserUsageConditions conditions; UserUsageConditions conditions;
WebAppInterface interface = fModel.GetWebAppInterface(); WebAppInterface* interface = fModel.GetWebAppInterface();
if (interface.RetrieveUserUsageConditions("", conditions) == B_OK) { if (interface->RetrieveUserUsageConditions("", conditions) == B_OK) {
BMessage userUsageConditionsMessage; BMessage userUsageConditionsMessage;
conditions.Archive(&userUsageConditionsMessage, true); conditions.Archive(&userUsageConditionsMessage, true);
BMessage dataMessage(MSG_USER_USAGE_CONDITIONS_DATA); BMessage dataMessage(MSG_USER_USAGE_CONDITIONS_DATA);
@@ -362,16 +362,15 @@ ToLatestUserUsageConditionsWindow::_AgreePerform()
{ {
BMessenger messenger(this); BMessenger messenger(this);
BMessage responsePayload; BMessage responsePayload;
WebAppInterface webApp = fModel.GetWebAppInterface(); WebAppInterface* webApp = fModel.GetWebAppInterface();
status_t result = webApp.AgreeUserUsageConditions( status_t result = webApp->AgreeUserUsageConditions(
fUserUsageConditions.Code(), responsePayload); fUserUsageConditions.Code(), responsePayload);
if (result != B_OK) { if (result != B_OK) {
ServerHelper::NotifyTransportError(result); ServerHelper::NotifyTransportError(result);
messenger.SendMessage(MSG_AGREE_FAILED); messenger.SendMessage(MSG_AGREE_FAILED);
} else { } else {
int32 errorCode = WebAppInterface::ErrorCodeFromResponse( int32 errorCode = WebAppInterface::ErrorCodeFromResponse(responsePayload);
responsePayload);
if (errorCode == ERROR_CODE_NONE) { if (errorCode == ERROR_CODE_NONE) {
AppUtils::NotifySimpleError( AppUtils::NotifySimpleError(
B_TRANSLATE("Usage conditions agreed"), B_TRANSLATE("Usage conditions agreed"),
+14 -16
View File
@@ -518,14 +518,14 @@ void
UserLoginWindow::_AuthenticateThread(UserCredentials& userCredentials) UserLoginWindow::_AuthenticateThread(UserCredentials& userCredentials)
{ {
BMessage responsePayload; BMessage responsePayload;
WebAppInterface interface = fModel.GetWebAppInterface(); WebAppInterface* interface = fModel.GetWebAppInterface();
status_t status = interface.AuthenticateUser( status_t status = interface->AuthenticateUser(
userCredentials.Nickname(), userCredentials.PasswordClear(), userCredentials.Nickname(), userCredentials.PasswordClear(),
responsePayload); responsePayload);
BString token; BString token;
if (status == B_OK) { if (status == B_OK) {
int32 errorCode = interface.ErrorCodeFromResponse(responsePayload); int32 errorCode = WebAppInterface::ErrorCodeFromResponse(responsePayload);
if (errorCode == ERROR_CODE_NONE) if (errorCode == ERROR_CODE_NONE)
_UnpackAuthenticationToken(responsePayload, token); _UnpackAuthenticationToken(responsePayload, token);
@@ -648,7 +648,7 @@ UserLoginWindow::_TakeUpCredentialsAndQuit(const UserCredentials& credentials)
{ {
{ {
AutoLocker<BLocker> locker(fModel.Lock()); AutoLocker<BLocker> locker(fModel.Lock());
fModel.SetAuthorization(credentials.Nickname(), fModel.SetCredentials(credentials.Nickname(),
credentials.PasswordClear(), true); credentials.PasswordClear(), true);
} }
@@ -815,9 +815,8 @@ status_t
UserLoginWindow::_CreateAccountUserUsageConditionsSetupThread( UserLoginWindow::_CreateAccountUserUsageConditionsSetupThread(
UserUsageConditions& userUsageConditions) UserUsageConditions& userUsageConditions)
{ {
WebAppInterface interface = fModel.GetWebAppInterface(); WebAppInterface* interface = fModel.GetWebAppInterface();
status_t result = interface.RetrieveUserUsageConditions( status_t result = interface->RetrieveUserUsageConditions(NULL, userUsageConditions);
NULL, userUsageConditions);
if (result != B_OK) { if (result != B_OK) {
AppUtils::NotifySimpleError( AppUtils::NotifySimpleError(
@@ -836,9 +835,8 @@ status_t
UserLoginWindow::_CreateAccountPasswordRequirementsSetupThread( UserLoginWindow::_CreateAccountPasswordRequirementsSetupThread(
PasswordRequirements& passwordRequirements) PasswordRequirements& passwordRequirements)
{ {
WebAppInterface interface = fModel.GetWebAppInterface(); WebAppInterface* interface = fModel.GetWebAppInterface();
status_t result = interface.RetrievePasswordRequirements( status_t result = interface->RetrievePasswordRequirements(passwordRequirements);
passwordRequirements);
if (result != B_OK) { if (result != B_OK) {
AppUtils::NotifySimpleError( AppUtils::NotifySimpleError(
@@ -856,10 +854,10 @@ UserLoginWindow::_CreateAccountPasswordRequirementsSetupThread(
status_t status_t
UserLoginWindow::_CreateAccountCaptchaSetupThread(Captcha& captcha) UserLoginWindow::_CreateAccountCaptchaSetupThread(Captcha& captcha)
{ {
WebAppInterface interface = fModel.GetWebAppInterface(); WebAppInterface* interface = fModel.GetWebAppInterface();
BMessage responsePayload; BMessage responsePayload;
status_t status = interface.RequestCaptcha(responsePayload); status_t status = interface->RequestCaptcha(responsePayload);
// check for transport related errors. // check for transport related errors.
@@ -873,7 +871,7 @@ UserLoginWindow::_CreateAccountCaptchaSetupThread(Captcha& captcha)
// check for server-generated errors. // check for server-generated errors.
if (status == B_OK) { if (status == B_OK) {
if (interface.ErrorCodeFromResponse(responsePayload) if (WebAppInterface::ErrorCodeFromResponse(responsePayload)
!= ERROR_CODE_NONE) { != ERROR_CODE_NONE) {
ServerHelper::AlertTransportError(&responsePayload); ServerHelper::AlertTransportError(&responsePayload);
status = B_ERROR; status = B_ERROR;
@@ -1307,11 +1305,11 @@ UserLoginWindow::_CreateAccountThreadEntry(void* data)
void void
UserLoginWindow::_CreateAccountThread(CreateUserDetail* detail) UserLoginWindow::_CreateAccountThread(CreateUserDetail* detail)
{ {
WebAppInterface interface = fModel.GetWebAppInterface(); WebAppInterface* interface = fModel.GetWebAppInterface();
BMessage responsePayload; BMessage responsePayload;
BMessenger messenger(this); BMessenger messenger(this);
status_t status = interface.CreateUser( status_t status = interface->CreateUser(
detail->Nickname(), detail->Nickname(),
detail->PasswordClear(), detail->PasswordClear(),
detail->Email(), detail->Email(),
@@ -1325,7 +1323,7 @@ UserLoginWindow::_CreateAccountThread(CreateUserDetail* detail)
"There was a puzzling response from the web service."); "There was a puzzling response from the web service.");
if (status == B_OK) { if (status == B_OK) {
int32 errorCode = interface.ErrorCodeFromResponse(responsePayload); int32 errorCode = WebAppInterface::ErrorCodeFromResponse(responsePayload);
switch (errorCode) { switch (errorCode) {
case ERROR_CODE_NONE: case ERROR_CODE_NONE:
@@ -270,7 +270,7 @@ UserUsageConditionsWindow::_FetchDataPerform()
{ {
UserDetail userDetail; UserDetail userDetail;
UserUsageConditions conditions; UserUsageConditions conditions;
WebAppInterface interface = fModel.GetWebAppInterface(); WebAppInterface* interface = fModel.GetWebAppInterface();
BString code; BString code;
status_t status = _FetchUserUsageConditionsCodePerform(userDetail, code); status_t status = _FetchUserUsageConditionsCodePerform(userDetail, code);
@@ -291,7 +291,7 @@ UserUsageConditionsWindow::_FetchDataPerform()
} }
if (status == B_OK) { if (status == B_OK) {
if (interface.RetrieveUserUsageConditions(code, conditions) == B_OK) { if (interface->RetrieveUserUsageConditions(code, conditions) == B_OK) {
BMessage userUsageConditionsMessage; BMessage userUsageConditionsMessage;
BMessage userDetailMessage; BMessage userDetailMessage;
conditions.Archive(&userUsageConditionsMessage, true); conditions.Archive(&userUsageConditionsMessage, true);
@@ -334,20 +334,19 @@ status_t
UserUsageConditionsWindow::_FetchUserUsageConditionsCodeForUserPerform( UserUsageConditionsWindow::_FetchUserUsageConditionsCodeForUserPerform(
UserDetail& userDetail, BString& code) 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" debugger("attempt to get user details for the current user, but"
" there is no current user"); " there is no current user");
BMessage responseEnvelopeMessage; BMessage responseEnvelopeMessage;
status_t result = interface.RetrieveCurrentUserDetail( status_t result = interface->RetrieveCurrentUserDetail(responseEnvelopeMessage);
responseEnvelopeMessage);
if (result == B_OK) { if (result == B_OK) {
// could be an error or could be a valid response envelope // could be an error or could be a valid response envelope
// containing data. // containing data.
switch (interface.ErrorCodeFromResponse(responseEnvelopeMessage)) { switch (WebAppInterface::ErrorCodeFromResponse(responseEnvelopeMessage)) {
case ERROR_CODE_NONE: case ERROR_CODE_NONE:
result = WebAppInterface::UnpackUserDetail( result = WebAppInterface::UnpackUserDetail(
responseEnvelopeMessage, userDetail); responseEnvelopeMessage, userDetail);
@@ -368,12 +367,12 @@ UserUsageConditionsWindow::_FetchUserUsageConditionsCodeForUserPerform(
if (result == B_OK) { if (result == B_OK) {
BString userUsageConditionsCode = userDetail.Agreement().Code(); BString userUsageConditionsCode = userDetail.Agreement().Code();
HDDEBUG("the user [%s] has agreed to uuc [%s]", HDDEBUG("the user [%s] has agreed to uuc [%s]",
interface.Nickname().String(), interface->Nickname().String(),
userUsageConditionsCode.String()); userUsageConditionsCode.String());
code.SetTo(userUsageConditionsCode); code.SetTo(userUsageConditionsCode);
} else { } else {
HDDEBUG("unable to get details of the user [%s]", HDDEBUG("unable to get details of the user [%s]",
interface.Nickname().String()); interface->Nickname().String());
} }
return result; return result;
+120 -1
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. * All rights reserved. Distributed under the terms of the MIT License.
*/ */
#include "DataIOUtils.h" #include "DataIOUtils.h"
@@ -31,6 +31,8 @@ DataIOUtils::CopyAll(BDataIO* target, BDataIO* source)
} }
// #pragma mark - ConstraintedDataIO
ConstraintedDataIO::ConstraintedDataIO(BDataIO* delegate, size_t limit) ConstraintedDataIO::ConstraintedDataIO(BDataIO* delegate, size_t limit)
: :
fDelegate(delegate), fDelegate(delegate),
@@ -71,3 +73,120 @@ ConstraintedDataIO::Flush()
{ {
return B_OK; 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;
}
+26 -1
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. * All rights reserved. Distributed under the terms of the MIT License.
*/ */
#ifndef DATA_IO_UTILS_H #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 #endif // DATA_IO_UTILS_H
@@ -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. * Distributed under the terms of the MIT License.
* *
* Authors: * Authors:
@@ -60,6 +60,7 @@ BHttpAuthentication::BHttpAuthentication(const BHttpAuthentication& other)
fAuthenticationMethod(other.fAuthenticationMethod), fAuthenticationMethod(other.fAuthenticationMethod),
fUserName(other.fUserName), fUserName(other.fUserName),
fPassword(other.fPassword), fPassword(other.fPassword),
fToken(other.fToken),
fRealm(other.fRealm), fRealm(other.fRealm),
fDigestNonce(other.fDigestNonce), fDigestNonce(other.fDigestNonce),
fDigestCnonce(other.fDigestCnonce), fDigestCnonce(other.fDigestCnonce),
@@ -79,6 +80,7 @@ BHttpAuthentication& BHttpAuthentication::operator=(
fAuthenticationMethod = other.fAuthenticationMethod; fAuthenticationMethod = other.fAuthenticationMethod;
fUserName = other.fUserName; fUserName = other.fUserName;
fPassword = other.fPassword; fPassword = other.fPassword;
fToken = other.fToken;
fRealm = other.fRealm; fRealm = other.fRealm;
fDigestNonce = other.fDigestNonce; fDigestNonce = other.fDigestNonce;
fDigestCnonce = other.fDigestCnonce; 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 void
BHttpAuthentication::SetMethod(BHttpAuthenticationMethod method) BHttpAuthentication::SetMethod(BHttpAuthenticationMethod method)
{ {
@@ -151,7 +162,9 @@ BHttpAuthentication::Initialize(const BString& wwwAuthenticate)
else if (authRequired == "digest") { else if (authRequired == "digest") {
fAuthenticationMethod = B_HTTP_AUTHENTICATION_DIGEST; fAuthenticationMethod = B_HTTP_AUTHENTICATION_DIGEST;
fDigestAlgorithm = B_HTTP_AUTHENTICATION_ALGORITHM_MD5; fDigestAlgorithm = B_HTTP_AUTHENTICATION_ALGORITHM_MD5;
} else } else if (authRequired == "bearer")
fAuthenticationMethod = B_HTTP_AUTHENTICATION_BEARER;
else
return B_ERROR; return B_ERROR;
@@ -206,6 +219,8 @@ BHttpAuthentication::Initialize(const BString& wwwAuthenticate)
if (fAuthenticationMethod == B_HTTP_AUTHENTICATION_BASIC) if (fAuthenticationMethod == B_HTTP_AUTHENTICATION_BASIC)
return B_OK; return B_OK;
else if (fAuthenticationMethod == B_HTTP_AUTHENTICATION_BEARER)
return B_OK;
else if (fAuthenticationMethod == B_HTTP_AUTHENTICATION_DIGEST else if (fAuthenticationMethod == B_HTTP_AUTHENTICATION_DIGEST
&& fDigestNonce.Length() > 0 && fDigestNonce.Length() > 0
&& fDigestAlgorithm != B_HTTP_AUTHENTICATION_ALGORITHM_NONE) { && fDigestAlgorithm != B_HTTP_AUTHENTICATION_ALGORITHM_NONE) {
@@ -260,6 +275,10 @@ BHttpAuthentication::Authorization(const BUrl& url, const BString& method) const
break; break;
} }
case B_HTTP_AUTHENTICATION_BEARER:
authorizationString << "Bearer " << fToken;
break;
case B_HTTP_AUTHENTICATION_DIGEST: case B_HTTP_AUTHENTICATION_DIGEST:
case B_HTTP_AUTHENTICATION_IE_DIGEST: case B_HTTP_AUTHENTICATION_IE_DIGEST:
authorizationString << "Digest " << "username=\"" << fUserName authorizationString << "Digest " << "username=\"" << fUserName
@@ -384,6 +403,7 @@ BHttpAuthentication::_DigestResponse(const BString& uri, const BString& method)
PRINT(("HttpAuth: Computing digest response: \n")); PRINT(("HttpAuth: Computing digest response: \n"));
PRINT(("HttpAuth: > username = %s\n", fUserName.String())); PRINT(("HttpAuth: > username = %s\n", fUserName.String()));
PRINT(("HttpAuth: > password = %s\n", fPassword.String())); PRINT(("HttpAuth: > password = %s\n", fPassword.String()));
PRINT(("HttpAuth: > token = %s\n", fToken.String()));
PRINT(("HttpAuth: > realm = %s\n", fRealm.String())); PRINT(("HttpAuth: > realm = %s\n", fRealm.String()));
PRINT(("HttpAuth: > nonce = %s\n", fDigestNonce.String())); PRINT(("HttpAuth: > nonce = %s\n", fDigestNonce.String()));
PRINT(("HttpAuth: > cnonce = %s\n", fDigestCnonce.String())); PRINT(("HttpAuth: > cnonce = %s\n", fDigestCnonce.String()));
@@ -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);
}
@@ -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
@@ -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. * Distributed under the terms of the MIT License.
*/ */
@@ -8,7 +8,9 @@
#include <TestSuiteAddon.h> #include <TestSuiteAddon.h>
#include "StandardMetaDataJsonEventListenerTest.h" #include "StandardMetaDataJsonEventListenerTest.h"
#include "DataIOUtilsTest.h"
#include "DumpExportRepositoryJsonListenerTest.h" #include "DumpExportRepositoryJsonListenerTest.h"
#include "JwtTokenHelperTest.h"
#include "ValidationFailureTest.h" #include "ValidationFailureTest.h"
#include "ValidationUtilsTest.h" #include "ValidationUtilsTest.h"
#include "StorageUtilsTest.h" #include "StorageUtilsTest.h"
@@ -21,7 +23,10 @@ getTestSuite()
BTestSuite* suite = new BTestSuite("HaikuDepot"); BTestSuite* suite = new BTestSuite("HaikuDepot");
StandardMetaDataJsonEventListenerTest::AddTests(*suite); StandardMetaDataJsonEventListenerTest::AddTests(*suite);
DataIOUtilsTest::AddTests(*suite);
DumpExportRepositoryJsonListenerTest::AddTests(*suite); DumpExportRepositoryJsonListenerTest::AddTests(*suite);
DumpExportRepositoryJsonListenerTest::AddTests(*suite);
JwtTokenHelperTest::AddTests(*suite);
ValidationFailureTest::AddTests(*suite); ValidationFailureTest::AddTests(*suite);
ValidationUtilsTest::AddTests(*suite); ValidationUtilsTest::AddTests(*suite);
StorageUtilsTest::AddTests(*suite); StorageUtilsTest::AddTests(*suite);
+4
View File
@@ -33,6 +33,7 @@ UnitTestLib haikudepottest.so :
HaikuDepotTestAddon.cpp HaikuDepotTestAddon.cpp
DataIOUtils.cpp DataIOUtils.cpp
DataIOUtilsTest.cpp
DumpExportRepositorySource.cpp DumpExportRepositorySource.cpp
DumpExportRepositorySourceMirror.cpp DumpExportRepositorySourceMirror.cpp
@@ -40,6 +41,9 @@ UnitTestLib haikudepottest.so :
DumpExportRepositoryJsonListener.cpp DumpExportRepositoryJsonListener.cpp
DumpExportRepositoryJsonListenerTest.cpp DumpExportRepositoryJsonListenerTest.cpp
JwtTokenHelper.cpp
JwtTokenHelperTest.cpp
Logger.cpp Logger.cpp
StandardMetaData.cpp StandardMetaData.cpp
@@ -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);
}
@@ -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