HaikuDepot: Refactor of Login

These changes cover a rework of the login and
account creation logic before making additional
changes related to the user usage conditions.

Relates to #15209

Change-Id: I90b7dbcee5b0285476938c6ced0afc89483d6227
Reviewed-on: https://review.haiku-os.org/c/haiku/+/2023
Reviewed-by: Stephan Aßmus <[email protected]>
Reviewed-by: Adrien Destugues <[email protected]>
This commit is contained in:
Andrew Lindesay
2020-02-03 08:51:55 +00:00
parent dcee1c8ce4
commit 835e7239d0
23 changed files with 2481 additions and 764 deletions
+4
View File
@@ -116,6 +116,8 @@ local applicationSources =
App.cpp
BarberPole.cpp
BitmapView.cpp
Captcha.cpp
CreateUserDetail.cpp
DecisionProvider.cpp
FeaturedPackagesView.cpp
FilterView.cpp
@@ -149,6 +151,8 @@ local applicationSources =
UserLoginWindow.cpp
UserUsageConditions.cpp
UserUsageConditionsWindow.cpp
ValidationFailure.cpp
ValidationUtils.cpp
WorkStatusView.cpp
# network + server / local processes
+100
View File
@@ -0,0 +1,100 @@
/*
* Copyright 2019, Andrew Lindesay <[email protected]>.
*
* All rights reserved. Distributed under the terms of the MIT License.
*/
#include "Captcha.h"
#include <stdio.h>
#include <DataIO.h>
// These are keys that are used to store this object's data into a BMessage
// instance.
#define KEY_TOKEN "token"
#define KEY_PNG_IMAGE_DATA "pngImageData"
Captcha::Captcha(BMessage* from)
:
fToken(""),
fPngImageData(NULL)
{
if (from->FindString(KEY_TOKEN, &fToken) != B_OK)
printf("expected key [%s] in the message data when creating a "
"Captcha\n", KEY_TOKEN);
const void* data;
ssize_t len;
if (from->FindData(KEY_PNG_IMAGE_DATA, B_ANY_TYPE, &data, &len) != B_OK)
printf("expected key [%s] in the message data\n", KEY_PNG_IMAGE_DATA);
else
SetPngImageData(data, len);
}
Captcha::Captcha()
:
fToken(""),
fPngImageData(NULL)
{
}
Captcha::~Captcha()
{
if (fPngImageData != NULL)
delete fPngImageData;
}
const BString&
Captcha::Token() const
{
return fToken;
}
BPositionIO*
Captcha::PngImageData() const
{
return fPngImageData;
}
void
Captcha::SetToken(const BString& value)
{
fToken = value;
}
void
Captcha::SetPngImageData(const void* data, size_t len)
{
if (fPngImageData != NULL)
delete fPngImageData;
fPngImageData = NULL;
if (data != NULL) {
fPngImageData = new BMallocIO();
fPngImageData->Write(data, len);
}
}
status_t
Captcha::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 && fPngImageData != NULL) {
result = into->AddData(KEY_PNG_IMAGE_DATA, B_ANY_TYPE,
fPngImageData->Buffer(), fPngImageData->BufferLength());
}
return result;
}
+41
View File
@@ -0,0 +1,41 @@
/*
* Copyright 2019, Andrew Lindesay <[email protected]>.
*
* All rights reserved. Distributed under the terms of the MIT License.
*/
#ifndef CAPTCHA_H
#define CAPTCHA_H
#include <Archivable.h>
#include <String.h>
class BPositionIO;
/*! When a user has to perform some sensitive operation, it is necessary to make
sure that it is not a 'robot' or software system that is acting as if it
were a person. It is necessary to know that a real person is acting. In
this case a graphical puzzle is presented to the user that presumably only
a human operator could solve. This is called a Captcha.
*/
class Captcha : public BArchivable {
public:
Captcha(BMessage* from);
Captcha();
virtual ~Captcha();
const BString& Token() const;
BPositionIO* PngImageData() const;
void SetToken(const BString& value);
void SetPngImageData(const void* data, size_t len);
status_t Archive(BMessage* into, bool deep = true) const;
private:
BString fToken;
BMallocIO* fPngImageData;
};
#endif // CAPTCHA_H
@@ -0,0 +1,182 @@
/*
* Copyright 2019, Andrew Lindesay <[email protected]>.
*
* All rights reserved. Distributed under the terms of the MIT License.
*/
#include "CreateUserDetail.h"
// These are keys that are used to store this object's data into a BMessage
// instance.
#define KEY_NICKNAME "nickname"
#define KEY_PASSWORD_CLEAR "passwordClear"
#define KEY_IS_PASSWORD_REPEATED "isPasswordRepeated"
#define KEY_EMAIL "email"
#define KEY_CAPTCHA_TOKEN "captchaToken"
#define KEY_CAPTCHA_RESPONSE "captchaResponse"
#define KEY_LANGUAGE_CODE "languageCode"
#define KEY_AGREED_USER_USAGE_CONDITIONS_CODE "agreedUserUsageConditionsCode"
CreateUserDetail::CreateUserDetail(BMessage* from)
{
from->FindString(KEY_NICKNAME, &fNickname);
from->FindString(KEY_PASSWORD_CLEAR, &fPasswordClear);
from->FindBool(KEY_IS_PASSWORD_REPEATED, &fIsPasswordRepeated);
from->FindString(KEY_EMAIL, &fEmail);
from->FindString(KEY_CAPTCHA_TOKEN, &fCaptchaToken);
from->FindString(KEY_CAPTCHA_RESPONSE, &fCaptchaResponse);
from->FindString(KEY_LANGUAGE_CODE, &fLanguageCode);
from->FindString(KEY_AGREED_USER_USAGE_CONDITIONS_CODE,
&fAgreedUserUsageConditionsCode);
}
CreateUserDetail::CreateUserDetail()
:
fIsPasswordRepeated(false)
{
}
CreateUserDetail::~CreateUserDetail()
{
}
const BString&
CreateUserDetail::Nickname() const
{
return fNickname;
}
const BString&
CreateUserDetail::PasswordClear() const
{
return fPasswordClear;
}
bool
CreateUserDetail::IsPasswordRepeated() const
{
return fIsPasswordRepeated;
}
const BString&
CreateUserDetail::Email() const
{
return fEmail;
}
const BString&
CreateUserDetail::CaptchaToken() const
{
return fCaptchaToken;
}
const BString&
CreateUserDetail::CaptchaResponse() const
{
return fCaptchaResponse;
}
const BString&
CreateUserDetail::LanguageCode() const
{
return fLanguageCode;
}
const BString&
CreateUserDetail::AgreedToUserUsageConditionsCode() const
{
return fAgreedUserUsageConditionsCode;
}
void
CreateUserDetail::SetNickname(const BString& value)
{
fNickname = value;
}
void
CreateUserDetail::SetPasswordClear(const BString& value)
{
fPasswordClear = value;
}
void
CreateUserDetail::SetIsPasswordRepeated(bool value)
{
fIsPasswordRepeated = value;
}
void
CreateUserDetail::SetEmail(const BString& value)
{
fEmail = value;
}
void
CreateUserDetail::SetCaptchaToken(const BString& value)
{
fCaptchaToken = value;
}
void
CreateUserDetail::SetCaptchaResponse(const BString& value)
{
fCaptchaResponse = value;
}
void
CreateUserDetail::SetLanguageCode(const BString& value)
{
fLanguageCode = value;
}
void
CreateUserDetail::SetAgreedToUserUsageConditionsCode(const BString& value)
{
fAgreedUserUsageConditionsCode = value;
}
status_t
CreateUserDetail::Archive(BMessage* into, bool deep) const
{
status_t result = B_OK;
if (result == B_OK)
result = into->AddString(KEY_NICKNAME, fNickname);
if (result == B_OK)
result = into->AddString(KEY_PASSWORD_CLEAR, fPasswordClear);
if (result == B_OK)
result = into->AddBool(KEY_IS_PASSWORD_REPEATED, fIsPasswordRepeated);
if (result == B_OK)
result = into->AddString(KEY_EMAIL, fEmail);
if (result == B_OK)
result = into->AddString(KEY_CAPTCHA_TOKEN, fCaptchaToken);
if (result == B_OK)
result = into->AddString(KEY_CAPTCHA_RESPONSE, fCaptchaResponse);
if (result == B_OK)
result = into->AddString(KEY_LANGUAGE_CODE, fLanguageCode);
if (result == B_OK) {
result = into->AddString(KEY_AGREED_USER_USAGE_CONDITIONS_CODE,
fAgreedUserUsageConditionsCode);
}
return result;
}
@@ -0,0 +1,59 @@
/*
* Copyright 2019, Andrew Lindesay <[email protected]>.
*
* All rights reserved. Distributed under the terms of the MIT License.
*/
#ifndef CREATE_USER_DETAIL_H
#define CREATE_USER_DETAIL_H
#include <Archivable.h>
#include <String.h>
/*! The operator may choose to create a User. In this case, he or she is
required to supply some details such as the nickname, password, email...
and those details are then collated and provided to the application server
(HDS) in order that the User is created in the application server. This
model carries all of those details in an object so that they might be
easily conveyed between methods.
*/
class CreateUserDetail : public BArchivable {
public:
CreateUserDetail(BMessage* from);
CreateUserDetail();
virtual ~CreateUserDetail();
const BString& Nickname() const;
const BString& PasswordClear() const;
bool IsPasswordRepeated() const;
const BString& Email() const;
const BString& CaptchaToken() const;
const BString& CaptchaResponse() const;
const BString& LanguageCode() const;
const BString& AgreedToUserUsageConditionsCode() const;
void SetNickname(const BString& value);
void SetPasswordClear(const BString& value);
void SetIsPasswordRepeated(bool value);
void SetEmail(const BString& value);
void SetCaptchaToken(const BString& value);
void SetCaptchaResponse(const BString& value);
void SetLanguageCode(const BString& value);
void SetAgreedToUserUsageConditionsCode(
const BString& value);
status_t Archive(BMessage* into, bool deep = true) const;
private:
BString fNickname;
BString fPasswordClear;
bool fIsPasswordRepeated;
BString fEmail;
BString fCaptchaToken;
BString fCaptchaResponse;
BString fLanguageCode;
BString fAgreedUserUsageConditionsCode;
};
#endif // CREATE_USER_DETAIL_H
@@ -32,6 +32,15 @@ UserCredentials::UserCredentials(const BString& nickname,
}
UserCredentials::UserCredentials(const UserCredentials& other)
:
fNickname(other.Nickname()),
fPasswordClear(other.PasswordClear()),
fIsSuccessful(false)
{
}
UserCredentials::UserCredentials()
:
fNickname(),
+5 -3
View File
@@ -11,9 +11,9 @@
#include <String.h>
/*! This object represents the tuple of the user's nickname (username) and
password. It also carries a boolean that indicates if an authentication
with these credentials was successful or failed.
/*! This object represents the tuple of the user's nickname (username) and
password. It also carries a boolean that indicates if an authentication
with these credentials was successful or failed.
*/
class UserCredentials : public BArchivable {
@@ -21,6 +21,7 @@ public:
UserCredentials(BMessage* from);
UserCredentials(const BString& nickname,
const BString& passwordClear);
UserCredentials(const UserCredentials& other);
UserCredentials();
virtual ~UserCredentials();
@@ -42,4 +43,5 @@ private:
bool fIsSuccessful;
};
#endif // USER_CREDENTIALS_H
@@ -11,11 +11,11 @@
#include <String.h>
/*! A user in the HDS system should have agreed to user usage conditions when
they created their user on the server. This object represents the user
usage conditions that either they have agreed to or that they could agree
to. Each set of user usage conditions has a code that uniquely identifies
a given set of conditions.
/*! A user in the HDS system should have agreed to user usage conditions when
they created their user on the server. This object represents the user
usage conditions that either they have agreed to or that they could agree
to. Each set of user usage conditions has a code that uniquely identifies
a given set of conditions.
*/
class UserUsageConditions : public BArchivable {
@@ -0,0 +1,230 @@
/*
* Copyright 2019, Andrew Lindesay <[email protected]>.
*
* All rights reserved. Distributed under the terms of the MIT License.
*/
#include "ValidationFailure.h"
// These are keys that are used to store this object's data into a BMessage
// instance.
#define KEY_PROPERTY "property"
#define KEY_PREFIX_MESSAGE "message_"
#define KEY_PREFIX_ITEM "item_"
// #pragma mark - Single Validation Failure
ValidationFailure::ValidationFailure(BMessage* from)
{
from->FindString(KEY_PROPERTY, &fProperty);
if (fProperty.IsEmpty())
debugger("illegal state; missing property in message");
status_t result = B_OK;
BString name;
BString message;
for (int32 i = 0; result == B_OK; i++) {
name.SetToFormat("%s%" B_PRId32, KEY_PREFIX_MESSAGE, i);
result = from->FindString(name, &message);
if (result == B_OK)
AddMessage(message);
}
}
ValidationFailure::ValidationFailure(const BString& property)
{
fProperty = property;
}
ValidationFailure::~ValidationFailure()
{
}
const BString&
ValidationFailure::Property() const
{
return fProperty;
}
const BStringList&
ValidationFailure::Messages() const
{
return fMessages;
}
bool
ValidationFailure::IsEmpty() const
{
return fMessages.IsEmpty();
}
bool
ValidationFailure::Contains(const BString& message) const
{
return fMessages.HasString(message);
}
void
ValidationFailure::AddMessage(const BString& value)
{
fMessages.Add(value);
}
status_t
ValidationFailure::Archive(BMessage* into, bool deep) const
{
status_t result = B_OK;
BString key;
if (result == B_OK)
result = into->AddString(KEY_PROPERTY, fProperty);
for (int32 i = 0; result == B_OK && i < fMessages.CountStrings(); i++) {
key.SetToFormat("%s%" B_PRId32, KEY_PREFIX_MESSAGE, i);
result = into->AddString(key, fMessages.StringAt(i));
}
return result;
}
// #pragma mark - Collections of Validation Failures
ValidationFailures::ValidationFailures(BMessage* from)
:
fItems(20, true)
{
_AddFromMessage(from);
}
ValidationFailures::ValidationFailures()
:
fItems(20, true)
{
}
ValidationFailures::~ValidationFailures()
{
}
void
ValidationFailures::AddFailure(const BString& property, const BString& message)
{
_GetOrCreateFailure(property)->AddMessage(message);
}
int32
ValidationFailures::CountFailures() const
{
return fItems.CountItems();
}
bool
ValidationFailures::IsEmpty() const
{
return fItems.IsEmpty();
}
bool
ValidationFailures::Contains(const BString& property) const
{
ValidationFailure* failure = _GetFailure(property);
return failure != NULL && !failure->IsEmpty();
}
bool
ValidationFailures::Contains(const BString& property,
const BString& message) const
{
ValidationFailure* failure = _GetFailure(property);
return failure != NULL && failure->Contains(message);
}
ValidationFailure*
ValidationFailures::FailureAtIndex(int32 index) const
{
return fItems.ItemAt(index);
}
status_t
ValidationFailures::Archive(BMessage* into, bool deep) const
{
status_t result = B_OK;
BString key;
for (int32 i = 0; i < fItems.CountItems() && result == B_OK; i++) {
ValidationFailure* item = fItems.ItemAt(i);
BMessage itemMessage;
result = item->Archive(&itemMessage);
if (result == B_OK) {
key.SetToFormat("%s%" B_PRId32, KEY_PREFIX_ITEM, i);
result = into->AddMessage(key, &itemMessage);
}
}
return result;
}
ValidationFailure*
ValidationFailures::_GetFailure(const BString& property) const
{
for (int32 i = 0; i < fItems.CountItems(); i++) {
ValidationFailure* item = fItems.ItemAt(i);
if (item->Property() == property)
return item;
}
return NULL;
}
ValidationFailure*
ValidationFailures::_GetOrCreateFailure(const BString& property)
{
ValidationFailure* item = _GetFailure(property);
if (item == NULL) {
item = new ValidationFailure(property);
fItems.AddItem(item);
}
return item;
}
void
ValidationFailures::_AddFromMessage(const BMessage* from)
{
int32 i = 0;
BString key;
while (true) {
BMessage itemMessage;
key.SetToFormat("%s%" B_PRId32, KEY_PREFIX_ITEM, i);
if (from->FindMessage(key, &itemMessage) != B_OK)
return;
fItems.AddItem(new ValidationFailure(&itemMessage));
i++;
}
}
@@ -0,0 +1,69 @@
/*
* Copyright 2019, Andrew Lindesay <[email protected]>.
*
* All rights reserved. Distributed under the terms of the MIT License.
*/
#ifndef VALIDATION_FAILURE_H
#define VALIDATION_FAILURE_H
#include <Archivable.h>
#include <ObjectList.h>
#include <String.h>
#include <StringList.h>
/*! This objects marries together a 'key' into some data together with an
error message. The error message is not intended to be human readable, but
is a key into a message.
*/
class ValidationFailure : public BArchivable {
public:
ValidationFailure(BMessage* from);
ValidationFailure(const BString& property);
virtual ~ValidationFailure();
const BString& Property() const;
const BStringList& Messages() const;
bool IsEmpty() const;
bool Contains(const BString& message) const;
void AddMessage(const BString& value);
status_t Archive(BMessage* into, bool deep = true) const;
private:
BString fProperty;
BStringList fMessages;
};
class ValidationFailures : public BArchivable {
public:
ValidationFailures(BMessage* from);
ValidationFailures();
virtual ~ValidationFailures();
void AddFailure(const BString& property,
const BString& message);
int32 CountFailures() const;
ValidationFailure* FailureAtIndex(int32 index) const;
bool IsEmpty() const;
bool Contains(const BString& property) const;
bool Contains(const BString& property,
const BString& message) const;
status_t Archive(BMessage* into, bool deep = true) const;
private:
void _AddFromMessage(const BMessage* from);
ValidationFailure* _GetFailure(const BString& property) const;
ValidationFailure* _GetOrCreateFailure(const BString& property);
private:
BObjectList<ValidationFailure>
fItems;
};
#endif // VALIDATION_FAILURE_H
+82 -7
View File
@@ -27,29 +27,30 @@
#define KEY_HEADER_MINIMUM_VERSION "X-Desktop-Application-Minimum-Version"
/*! This method will cause an alert to be shown to the user regarding a
/*! \brief This method will cause an alert to be shown to the user regarding a
JSON-RPC error that has been sent from the application server. It will
send a message to the application looper which will then relay the message
to the looper and then onto the user to see.
\param responsePayload The top level payload returned from the server.
*/
/*static*/ void
ServerHelper::NotifyServerJsonRpcError(BMessage& error)
ServerHelper::NotifyServerJsonRpcError(BMessage& responsePayload)
{
BMessage message(MSG_SERVER_ERROR);
message.AddMessage("error", &error);
message.AddMessage("error", &responsePayload);
be_app->PostMessage(&message);
}
/*static*/ void
ServerHelper::AlertServerJsonRpcError(BMessage* message)
ServerHelper::AlertServerJsonRpcError(BMessage* responseEnvelopeMessage)
{
BMessage error;
BMessage errorMessage;
int32 errorCode = 0;
if (message->FindMessage("error", &error) == B_OK)
errorCode = WebAppInterface::ErrorCodeFromResponse(error);
if (responseEnvelopeMessage->FindMessage("error", &errorMessage) == B_OK)
errorCode = WebAppInterface::ErrorCodeFromResponse(errorMessage);
BString alertText;
@@ -210,4 +211,78 @@ ServerHelper::IsPlatformNetworkAvailable()
}
return false;
}
/*! If the response is an error and the error is a validation failure then
* various validation errors may be carried in the error data. These are
* copied into the supplied failures. An abridged example input JSON structure
* would be;
*
* \code
* {
* ...
* "error": {
* "code": -32800,
* "data": {
"validationfailures": [
{ "property": "nickname", "message": "required" },
...
]
},
...
* }
* }
* \endcode
*
* \param failures is the object into which the validation failures are to be
* written.
* \param responseEnvelopeMessage is a representation of the entire JSON-RPC
* response sent back from the server when the error occurred.
*
*/
/*static*/ void
ServerHelper::GetFailuresFromJsonRpcError(
ValidationFailures& failures, BMessage& responseEnvelopeMessage)
{
BMessage errorMessage;
int32 errorCode = WebAppInterface::ErrorCodeFromResponse(
responseEnvelopeMessage);
if (responseEnvelopeMessage.FindMessage("error", &errorMessage) == B_OK) {
BMessage dataMessage;
if (errorMessage.FindMessage("data", &dataMessage) == B_OK) {
BMessage validationFailuresMessage;
if (dataMessage.FindMessage("validationfailures",
&validationFailuresMessage) == B_OK) {
_GetFailuresFromJsonRpcFailures(failures,
validationFailuresMessage);
}
}
}
}
/*static*/ void
ServerHelper::_GetFailuresFromJsonRpcFailures(
ValidationFailures& failures, BMessage& jsonRpcFailures)
{
int32 index = 0;
while (true) {
BString name;
name << index++;
BMessage failure;
if (jsonRpcFailures.FindMessage(name, &failure) != B_OK)
break;
BString property;
BString message;
if (failure.FindString("property", &property) == B_OK
&& failure.FindString("message", &message) == B_OK) {
failures.AddFailure(property, message);
}
}
}
+12 -2
View File
@@ -1,5 +1,5 @@
/*
* Copyright 2017-2018, Andrew Lindesay <[email protected]>.
* Copyright 2017-2019, Andrew Lindesay <[email protected]>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
#ifndef SERVER_HELPER_H
@@ -7,6 +7,8 @@
#include <HttpHeaders.h>
#include "ValidationFailure.h"
class BMessage;
@@ -27,7 +29,15 @@ public:
static void NotifyServerJsonRpcError(
BMessage& error);
static void AlertServerJsonRpcError(
BMessage* message);
BMessage* responseEnvelopeMessage);
static void GetFailuresFromJsonRpcError(
ValidationFailures& failures,
BMessage& responseEnvelopeMessage);
private:
static void _GetFailuresFromJsonRpcFailures(
ValidationFailures& failures,
BMessage& jsonRpcFailures);
};
#endif // SERVER_HELPER_H
+80 -215
View File
@@ -8,29 +8,22 @@
#include <stdio.h>
#include <AppFileInfo.h>
#include <Application.h>
#include <AutoDeleter.h>
#include <Autolock.h>
#include <File.h>
#include <HttpHeaders.h>
#include <HttpRequest.h>
#include <Json.h>
#include <JsonTextWriter.h>
#include <JsonMessageWriter.h>
#include <Message.h>
#include <Roster.h>
#include <Url.h>
#include <UrlContext.h>
#include <UrlProtocolListener.h>
#include <UrlProtocolRoster.h>
#include "AutoLocker.h"
#include "DataIOUtils.h"
#include "HaikuDepotConstants.h"
#include "List.h"
#include "Logger.h"
#include "PackageInfo.h"
#include "ServerSettings.h"
#include "ServerHelper.h"
@@ -40,159 +33,6 @@
#define LOG_PAYLOAD_LIMIT 8192
class JsonBuilder {
public:
JsonBuilder()
:
fString("{"),
fInList(false)
{
}
JsonBuilder& AddObject()
{
fString << '{';
fInList = false;
return *this;
}
JsonBuilder& AddObject(const char* name)
{
_StartName(name);
fString << '{';
fInList = false;
return *this;
}
JsonBuilder& EndObject()
{
fString << '}';
fInList = true;
return *this;
}
JsonBuilder& AddArray(const char* name)
{
_StartName(name);
fString << '[';
fInList = false;
return *this;
}
JsonBuilder& EndArray()
{
fString << ']';
fInList = true;
return *this;
}
JsonBuilder& AddStrings(const StringList& strings)
{
for (int i = 0; i < strings.CountItems(); i++)
AddItem(strings.ItemAtFast(i));
return *this;
}
JsonBuilder& AddItem(const char* item)
{
return AddItem(item, false);
}
JsonBuilder& AddItem(const char* item, bool nullIfEmpty)
{
if (item == NULL || (nullIfEmpty && strlen(item) == 0)) {
if (fInList)
fString << ",null";
else
fString << "null";
} else {
if (fInList)
fString << ",\"";
else
fString << '"';
fString << _EscapeString(item);
fString << '"';
}
fInList = true;
return *this;
}
JsonBuilder& AddValue(const char* name, const char* value)
{
return AddValue(name, value, false);
}
JsonBuilder& AddValue(const char* name, const char* value,
bool nullIfEmpty)
{
_StartName(name);
if (value == NULL || (nullIfEmpty && strlen(value) == 0)) {
fString << "null";
} else {
fString << '"';
fString << _EscapeString(value);
fString << '"';
}
fInList = true;
return *this;
}
JsonBuilder& AddValue(const char* name, int value)
{
_StartName(name);
fString << value;
fInList = true;
return *this;
}
JsonBuilder& AddValue(const char* name, bool value)
{
_StartName(name);
if (value)
fString << "true";
else
fString << "false";
fInList = true;
return *this;
}
const BString& End()
{
fString << "}\n";
return fString;
}
private:
void _StartName(const char* name)
{
if (fInList)
fString << ",\"";
else
fString << '"';
fString << _EscapeString(name);
fString << "\":";
}
BString _EscapeString(const char* original) const
{
BString string(original);
string.ReplaceAll("\\", "\\\\");
string.ReplaceAll("\"", "\\\"");
string.ReplaceAll("/", "\\/");
string.ReplaceAll("\b", "\\b");
string.ReplaceAll("\f", "\\f");
string.ReplaceAll("\n", "\\n");
string.ReplaceAll("\r", "\\r");
string.ReplaceAll("\t", "\\t");
return string;
}
private:
BString fString;
bool fInList;
};
class ProtocolListener : public BUrlProtocolListener {
public:
ProtocolListener(bool traceLogging)
@@ -314,18 +154,27 @@ WebAppInterface::Nickname() const
status_t
WebAppInterface::GetChangelog(const BString& packageName, BMessage& message)
{
BString jsonString = JsonBuilder()
.AddValue("jsonrpc", "2.0")
.AddValue("id", ++fRequestIndex)
.AddValue("method", "getPkgChangelog")
.AddArray("params")
.AddObject()
.AddValue("pkgName", packageName)
.EndObject()
.EndArray()
.End();
BMallocIO* requestEnvelopeData = new BMallocIO();
// BHttpRequest later takes ownership of this.
BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData);
return _SendJsonRequest("pkg", jsonString, 0, message);
requestEnvelopeWriter.WriteObjectStart();
_WriteStandardJsonRpcEnvelopeValues(requestEnvelopeWriter,
"getPkgChangelog");
requestEnvelopeWriter.WriteObjectName("params");
requestEnvelopeWriter.WriteArrayStart();
requestEnvelopeWriter.WriteObjectStart();
requestEnvelopeWriter.WriteObjectName("pkgName");
requestEnvelopeWriter.WriteString(packageName.String());
requestEnvelopeWriter.WriteObjectEnd();
requestEnvelopeWriter.WriteArrayEnd();
requestEnvelopeWriter.WriteObjectEnd();
return _SendJsonRequest("pkg", requestEnvelopeData,
_LengthAndSeekToZero(requestEnvelopeData), 0,
message);
}
@@ -429,10 +278,10 @@ WebAppInterface::RetreiveUserRatingForPackageAndVersionByUser(
}
/*! This method will fill out the supplied UserDetail object with information
about the user that is supplied in the credentials. Importantly it will
also authenticate the request with the details of the credentials and will
not use the credentials that are configured in 'fCredentials'.
/*! This method will fill out the supplied UserDetail object with information
about the user that is supplied in the credentials. Importantly it will
also authenticate the request with the details of the credentials and will
not use the credentials that are configured in 'fCredentials'.
*/
status_t
@@ -468,8 +317,8 @@ WebAppInterface::RetrieveUserDetailForCredentials(
}
/*! This method will return the credentials for the currently authenticated
user.
/*! This method will return the credentials for the currently authenticated
user.
*/
status_t
@@ -479,9 +328,9 @@ WebAppInterface::RetrieveCurrentUserDetail(BMessage& message)
}
/*! When the user requests user detail, the server sends back an envelope of
response data. This method will unpack the data into a model object.
\return Not B_OK if something went wrong.
/*! When the user requests user detail, the server sends back an envelope of
response data. This method will unpack the data into a model object.
\return Not B_OK if something went wrong.
*/
/*static*/ status_t
@@ -533,14 +382,14 @@ WebAppInterface::UnpackUserDetail(BMessage& responseEnvelopeMessage,
}
/*! \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
latest is returned.
This method will go to the server and get details relating to the user usage
conditions. It does this in two API calls; first gets the details (the
minimum age) and in the second call, the text of the conditions is returned.
This method will go to the server and get details relating to the user usage
conditions. It does this in two API calls; first gets the details (the
minimum age) and in the second call, the text of the conditions is returned.
*/
status_t
@@ -782,17 +631,23 @@ WebAppInterface::RetrieveScreenshot(const BString& code,
status_t
WebAppInterface::RequestCaptcha(BMessage& message)
{
BString jsonString = JsonBuilder()
.AddValue("jsonrpc", "2.0")
.AddValue("id", ++fRequestIndex)
.AddValue("method", "generateCaptcha")
.AddArray("params")
.AddObject()
.EndObject()
.EndArray()
.End();
BMallocIO* requestEnvelopeData = new BMallocIO();
// BHttpRequest later takes ownership of this.
BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData);
return _SendJsonRequest("captcha", jsonString, 0, message);
requestEnvelopeWriter.WriteObjectStart();
_WriteStandardJsonRpcEnvelopeValues(requestEnvelopeWriter,
"generateCaptcha");
requestEnvelopeWriter.WriteObjectName("params");
requestEnvelopeWriter.WriteArrayStart();
requestEnvelopeWriter.WriteObjectStart();
requestEnvelopeWriter.WriteObjectEnd();
requestEnvelopeWriter.WriteArrayEnd();
requestEnvelopeWriter.WriteObjectEnd();
return _SendJsonRequest("captcha", requestEnvelopeData,
_LengthAndSeekToZero(requestEnvelopeData), 0,
message);
}
@@ -845,28 +700,38 @@ status_t
WebAppInterface::AuthenticateUser(const BString& nickName,
const BString& passwordClear, BMessage& message)
{
BString jsonString = JsonBuilder()
.AddValue("jsonrpc", "2.0")
.AddValue("id", ++fRequestIndex)
.AddValue("method", "authenticateUser")
.AddArray("params")
.AddObject()
.AddValue("nickname", nickName)
.AddValue("passwordClear", passwordClear)
.EndObject()
.EndArray()
.End();
BMallocIO* requestEnvelopeData = new BMallocIO();
// BHttpRequest later takes ownership of this.
BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData);
return _SendJsonRequest("user", jsonString, 0, message);
requestEnvelopeWriter.WriteObjectStart();
_WriteStandardJsonRpcEnvelopeValues(requestEnvelopeWriter,
"authenticateUser");
requestEnvelopeWriter.WriteObjectName("params");
requestEnvelopeWriter.WriteArrayStart();
requestEnvelopeWriter.WriteObjectStart();
requestEnvelopeWriter.WriteObjectName("nickname");
requestEnvelopeWriter.WriteString(nickName.String());
requestEnvelopeWriter.WriteObjectName("passwordClear");
requestEnvelopeWriter.WriteString(passwordClear.String());
requestEnvelopeWriter.WriteObjectEnd();
requestEnvelopeWriter.WriteArrayEnd();
requestEnvelopeWriter.WriteObjectEnd();
return _SendJsonRequest("user", requestEnvelopeData,
_LengthAndSeekToZero(requestEnvelopeData), 0,
message);
}
/*! JSON-RPC invocations return a response. The response may be either
a result or it may be an error depending on the response structure.
If it is an error then there may be additional detail that is the
error code and message. This method will extract the error code
from the response. This method will return 0 if the payload does
not look like an error.
/*! JSON-RPC invocations return a response. The response may be either
a result or it may be an error depending on the response structure.
If it is an error then there may be additional detail that is the
error code and message. This method will extract the error code
from the response. This method will return 0 if the payload does
not look like an error.
*/
int32
@@ -1107,9 +972,9 @@ WebAppInterface::_LogPayload(BPositionIO* requestData, size_t size)
}
/*! This will get the position of the data to get the length an then sets the
offset to zero so that it can be re-read for reading the payload in to log
or send.
/*! This will get the position of the data to get the length an then sets the
offset to zero so that it can be re-read for reading the payload in to log
or send.
*/
off_t
File diff suppressed because it is too large Load Diff
+66 -22
View File
@@ -10,7 +10,10 @@
#include <Messenger.h>
#include <Window.h>
#include "CreateUserDetail.h"
#include "PackageInfo.h"
#include "UserCredentials.h"
#include "ValidationFailure.h"
class BButton;
@@ -19,6 +22,7 @@ class BMenuField;
class BTabView;
class BTextControl;
class BitmapView;
class Captcha;
class LinkView;
class Model;
class UserUsageConditions;
@@ -30,6 +34,7 @@ public:
Model& model);
virtual ~UserLoginWindow();
virtual bool QuitRequested();
virtual void MessageReceived(BMessage* message);
void SetOnSuccessMessage(
@@ -45,45 +50,85 @@ private:
};
void _SetMode(Mode mode);
bool _ValidateCreateAccountFields(
bool alertProblems = false);
void _Login();
void _SetWorkerThread(thread_id thread);
void _SetWorkerThreadLocked(thread_id thread);
void _Authenticate();
void _Authenticate(
const UserCredentials& credentials);
static int32 _AuthenticateThreadEntry(void* data);
void _AuthenticateThread(
UserCredentials& credentials);
void _UnpackAuthenticationToken(
BMessage& responsePayload, BString& token);
void _HandleAuthenticationFailed();
void _HandleAuthenticationSuccess(
const UserCredentials & credentials);
void _HandleAuthenticationError();
void _CreateAccount();
void _AssembleCreateUserDetail(
CreateUserDetail& detail);
void _ValidateCreateUserDetail(
CreateUserDetail& detail,
ValidationFailures& failures);
void _AlertCreateUserValidationFailure(
const ValidationFailures& failures);
static BString _CreateAlertTextFromValidationFailure(
const BString& property,
const BString& message);
void _MarkCreateUserInvalidFields();
void _MarkCreateUserInvalidFields(
const ValidationFailures& failures);
static int32 _CreateAccountThreadEntry(void* data);
void _CreateAccountThread(CreateUserDetail* detail);
void _HandleCreateAccountSuccess(
const UserCredentials& credentials);
void _HandleCreateAccountFailure(
const ValidationFailures& failures);
void _HandleCreateAccountError();
void _CreateAccountSetup(uint32 mask);
void _CreateAccountSetupIfNecessary();
void _LoginSuccessful(const BString& message);
void _SetWorkerThread(thread_id thread);
static int32 _AuthenticateThreadEntry(void* data);
void _AuthenticateThread();
static int32 _CreateAccountSetupThreadEntry(void* data);
void _CreateAccountCaptchaSetupThread();
void _CreateAccountUserUsageConditionsSetupThread();
status_t _CreateAccountCaptchaSetupThread(
Captcha& captcha);
status_t _CreateAccountUserUsageConditionsSetupThread(
UserUsageConditions& userUsageConditions);
status_t _UnpackCaptcha(BMessage& responsePayload,
Captcha& captcha);
void _HandleCreateAccountSetupSuccess(
BMessage* message);
void _SetCaptcha(Captcha* captcha);
void _SetUserUsageConditions(
UserUsageConditions* userUsageConditions);
static int32 _CreateAccountThreadEntry(void* data);
void _CreateAccountThread();
void _CollectValidationFailures(
const BMessage& result,
BString& error) const;
void _ViewUserUsageConditions();
void _TakeUpCredentialsAndQuit(
const UserCredentials& credentials);
void _EnableMutableControls(bool enabled);
static void _ValidationFailuresToString(
const ValidationFailures& failures,
BString& output);
private:
BMessenger fOnSuccessTarget;
BMessage fOnSuccessMessage;
BTabView* fTabView;
BTextControl* fUsernameField;
BTextControl* fNicknameField;
BTextControl* fPasswordField;
BTextControl* fNewUsernameField;
BTextControl* fNewNicknameField;
BTextControl* fNewPasswordField;
BTextControl* fRepeatPasswordField;
BTextControl* fEmailField;
@@ -97,19 +142,18 @@ private:
BButton* fSendButton;
BButton* fCancelButton;
BString fCaptchaToken;
BitmapRef fCaptchaImage;
UserUsageConditions*
fUserUsageConditions;
Captcha* fCaptcha;
BString fPreferredLanguageCode;
Model& fModel;
Mode fMode;
UserUsageConditions*
fUserUsageConditions;
BLocker fLock;
thread_id fWorkerThread;
bool fQuitRequestedDuringWorkerThread;
};
@@ -0,0 +1,130 @@
/*
* Copyright 2019, Andrew Lindesay <apl@lindesay.co.nz>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
#include "ValidationUtils.h"
#include <ctype.h>
#include <UnicodeChar.h>
#define MIN_LENGTH_NICKNAME 4
#define MAX_LENGTH_NICKNAME 16
#define MIN_LENGTH_PASSWORD_CLEAR 8
#define MIN_UPPER_PASSWORD_CLEAR 2
#define MIN_DIGITS_PASSWORD_CLEAR 2
/*! 1 if the character would be suitable for use in an email address mailbox
or domain part.
*/
static int
hd_is_email_domain_or_mailbox_part(int c)
{
if (0 == isspace(c) && c != 0x40)
return 1;
return 0;
}
/*! Returns true if the entire string is lower case alpha numeric.
*/
static int
hd_is_lower_alnum(int c)
{
if ((c >= 0x30 && c <= 0x39) || (c >= 0x60 && c <= 0x7a))
return 1;
return 0;
}
static bool
hd_str_all_matches_fn(const BString& string, int (*hd_match_c)(int c))
{
const char* c = string.String();
for (int32 i = 0; i < string.CountChars(); i++) {
if (0 == hd_match_c(c[i]))
return false;
}
return true;
}
static int32
hd_str_count_upper_case(const BString& string)
{
int32 upperCaseLetters = 0;
const char* c = string.String();
for (int32 i = 0; i < string.CountChars(); i++) {
uint32 unicodeChar = BUnicodeChar::FromUTF8(&c);
if (BUnicodeChar::IsUpper(unicodeChar))
upperCaseLetters++;
}
return upperCaseLetters;
}
static int32
hd_str_count_digit(const BString& string)
{
int32 digits = 0;
const char* c = string.String();
for (int32 i = 0; i < string.CountChars(); i++) {
uint32 unicodeChar = BUnicodeChar::FromUTF8(&c);
if (BUnicodeChar::IsDigit(unicodeChar))
digits++;
}
return digits;
}
/*static*/ bool
ValidationUtils::IsValidNickname(const BString& value)
{
return hd_str_all_matches_fn(value, &hd_is_lower_alnum)
&& value.CountChars() >= MIN_LENGTH_NICKNAME
&& value.CountChars() <= MAX_LENGTH_NICKNAME;
}
/*! Email addresses are quite difficult to validate 100% correctly so go fairly
light on the enforcement here; it should be a string with an '@' symbol,
something either side of the '@' and there should be no whitespace.
*/
/*static*/ bool
ValidationUtils::IsValidEmail(const BString& value)
{
const char* c = value.String();
size_t len = strlen(c);
bool foundAt = false;
for (size_t i = 0; i < len; i++) {
if (c[i] == 0x40 && !foundAt) {
if (i == 0 || i == len - 1)
return false;
foundAt = true;
}
else {
if (0 == hd_is_email_domain_or_mailbox_part(c[i]))
return false;
}
}
return foundAt;
}
/*static*/ bool
ValidationUtils::IsValidPasswordClear(const BString& value)
{
return value.Length() >= MIN_LENGTH_PASSWORD_CLEAR
&& hd_str_count_digit(value) >= MIN_DIGITS_PASSWORD_CLEAR
&& hd_str_count_upper_case(value) >= MIN_UPPER_PASSWORD_CLEAR;
}
@@ -0,0 +1,20 @@
/*
* Copyright 2019, Andrew Lindesay <apl@lindesay.co.nz>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
#ifndef VALIDATION_UTILS_H
#define VALIDATION_UTILS_H
#include <String.h>
class ValidationUtils {
public:
static bool IsValidEmail(const BString& value);
static bool IsValidNickname(const BString& value);
static bool IsValidPasswordClear(const BString& value);
};
#endif // VALIDATION_UTILS_H
@@ -1,5 +1,5 @@
/*
* Copyright 2017, Andrew Lindesay, apl@lindesay.co.nz
* Copyright 2017-2019, Andrew Lindesay, apl@lindesay.co.nz
* Distributed under the terms of the MIT License.
*/
@@ -8,6 +8,8 @@
#include "StandardMetaDataJsonEventListenerTest.h"
#include "DumpExportRepositoryJsonListenerTest.h"
#include "ValidationFailureTest.h"
#include "ValidationUtilsTest.h"
#include "ListTest.h"
BTestSuite*
@@ -17,6 +19,8 @@ getTestSuite()
StandardMetaDataJsonEventListenerTest::AddTests(*suite);
DumpExportRepositoryJsonListenerTest::AddTests(*suite);
ValidationFailureTest::AddTests(*suite);
ValidationUtilsTest::AddTests(*suite);
ListTest::AddTests(*suite);
return suite;
+10
View File
@@ -3,14 +3,18 @@ SubDir HAIKU_TOP src tests apps haikudepot ;
AddSubDirSupportedPlatforms libbe_test ;
SubDirHdrs [ FDirName $(HAIKU_TOP) src apps haikudepot ] ;
SubDirHdrs [ FDirName $(HAIKU_TOP) src apps haikudepot model ] ;
SubDirHdrs [ FDirName $(HAIKU_TOP) src apps haikudepot server ] ;
SubDirHdrs [ FDirName $(HAIKU_TOP) src apps haikudepot server dumpexportrepository ] ;
SubDirHdrs [ FDirName $(HAIKU_TOP) src apps haikudepot util ] ;
UsePrivateHeaders shared ;
local sourceDirs =
model
server
server/dumpexportrepository
util
;
local sourceDir ;
@@ -37,5 +41,11 @@ UnitTestLib haikudepottest.so :
StandardMetaDataJsonEventListener.cpp
StandardMetaDataJsonEventListenerTest.cpp
ValidationFailure.cpp
ValidationFailureTest.cpp
ValidationUtils.cpp
ValidationUtilsTest.cpp
: be shared bnetapi package [ TargetLibstdc++ ] [ TargetLibsupc++ ]
;
@@ -0,0 +1,165 @@
/*
* Copyright 2019, Andrew Lindesay <apl@lindesay.co.nz>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
#include "ValidationFailureTest.h"
#include <cppunit/TestCaller.h>
#include <cppunit/TestSuite.h>
#include "ValidationFailure.h"
ValidationFailureTest::ValidationFailureTest()
{
}
ValidationFailureTest::~ValidationFailureTest()
{
}
void
ValidationFailureTest::TestDearchive()
{
BMessage nicknameMessage;
nicknameMessage.AddString("property", "nickname");
nicknameMessage.AddString("message_0", "malformed");
nicknameMessage.AddString("message_1", "required");
BMessage passwordClearMessage;
passwordClearMessage.AddString("property", "passwordClear");
passwordClearMessage.AddString("message_0", "required");
BMessage validationFailuresMessage;
validationFailuresMessage.AddMessage("item_0", &nicknameMessage);
validationFailuresMessage.AddMessage("item_1", &passwordClearMessage);
// ----------------------
ValidationFailures validationFailures(&validationFailuresMessage);
// ----------------------
CPPUNIT_ASSERT_EQUAL_MESSAGE("!Count failures", (int32) 2,
validationFailures.CountFailures());
CPPUNIT_ASSERT_EQUAL_MESSAGE("!Contains 'nickname'", true,
validationFailures.Contains("nickname"));
CPPUNIT_ASSERT_EQUAL_MESSAGE("!Contains 'nickname:required'", true,
validationFailures.Contains("nickname", "required"));
CPPUNIT_ASSERT_EQUAL_MESSAGE("!Contains 'nickname:malformed'", true,
validationFailures.Contains("nickname", "malformed"));
CPPUNIT_ASSERT_EQUAL_MESSAGE("!Contains 'passwordClear:required'", true,
validationFailures.Contains("passwordClear", "required"));
}
void
ValidationFailureTest::TestArchive()
{
ValidationFailures failures;
failures.AddFailure("nickname", "malformed");
failures.AddFailure("nickname", "required");
failures.AddFailure("passwordClear", "required");
BMessage validationFailuresMessage;
// ----------------------
status_t archiveResult = failures.Archive(&validationFailuresMessage);
// ----------------------
CPPUNIT_ASSERT_EQUAL_MESSAGE("!Archive failure", B_OK, archiveResult);
BMessage validationFailureNicknameMessage;
BMessage validationFailurePasswordClearMessage;
CPPUNIT_ASSERT_EQUAL_MESSAGE("!Unable to find 'nickname'", B_OK,
FindMessageWithProperty("nickname", validationFailuresMessage,
validationFailureNicknameMessage));
CPPUNIT_ASSERT_EQUAL_MESSAGE("!Unable to find 'passwordClear'", B_OK,
FindMessageWithProperty("passwordClear", validationFailuresMessage,
validationFailurePasswordClearMessage));
BStringList validationFailureMessagesNickname;
BStringList validationFailureMessagesPasswordClear;
FindValidationMessages(validationFailureNicknameMessage,
validationFailureMessagesNickname);
FindValidationMessages(validationFailurePasswordClearMessage,
validationFailureMessagesPasswordClear);
CPPUNIT_ASSERT_EQUAL_MESSAGE("!Unable to find 'nickname:malformed'",
true, validationFailureMessagesNickname.HasString("malformed"));
CPPUNIT_ASSERT_EQUAL_MESSAGE("!Unable to find 'nickname:required'",
true, validationFailureMessagesNickname.HasString("required"));
CPPUNIT_ASSERT_EQUAL_MESSAGE("!Unexpected validation messages 'nickname'",
(int32) 2, validationFailureMessagesNickname.CountStrings());
CPPUNIT_ASSERT_EQUAL_MESSAGE("!Unable to find 'passwordClear:required'",
true, validationFailureMessagesPasswordClear.HasString("required"));
CPPUNIT_ASSERT_EQUAL_MESSAGE("!Unexpected validation messages 'nickname'",
(int32) 1, validationFailureMessagesPasswordClear.CountStrings());
}
/*static*/ void
ValidationFailureTest::AddTests(BTestSuite& parent)
{
CppUnit::TestSuite& suite = *new CppUnit::TestSuite(
"ValidationFailureTest");
suite.addTest(
new CppUnit::TestCaller<ValidationFailureTest>(
"ValidationFailureTest::TestArchive",
&ValidationFailureTest::TestArchive));
suite.addTest(
new CppUnit::TestCaller<ValidationFailureTest>(
"ValidationFailureTest::TestDearchive",
&ValidationFailureTest::TestDearchive));
parent.addTest("ValidationFailureTest", &suite);
}
/*static*/ status_t
ValidationFailureTest::FindMessageWithProperty(
const char* property, BMessage& validationFailuresMessage,
BMessage& validationFailureMessage)
{
status_t result = B_OK;
for (int32 i = 0; result == B_OK; i++) {
BString name = "item_";
name << i;
result = validationFailuresMessage.FindMessage(name,
&validationFailureMessage);
if (result == B_OK) {
BString messageProperty;
result = validationFailureMessage.FindString("property",
&messageProperty);
if (result == B_OK && messageProperty == property)
return result;
}
}
return result;
}
/*static*/ void
ValidationFailureTest::FindValidationMessages(
BMessage& validationFailureMessage, BStringList& validationMessages)
{
status_t result = B_OK;
for (int32 i = 0; result == B_OK; i++) {
BString validationMessage;
BString name = "message_";
name << i;
result = validationFailureMessage.FindString(name,
&validationMessage);
if (result == B_OK) {
validationMessages.Add(validationMessage);
}
}
}
@@ -0,0 +1,35 @@
/*
* Copyright 2019, Andrew Lindesay <apl@lindesay.co.nz>
* Distributed under the terms of the MIT License.
*/
#ifndef VALIDATION_FAILURE_TEST_H
#define VALIDATION_FAILURE_TEST_H
#include "Message.h"
#include <TestCase.h>
#include <TestSuite.h>
class ValidationFailureTest : public CppUnit::TestCase {
public:
ValidationFailureTest();
virtual ~ValidationFailureTest();
void TestArchive();
void TestDearchive();
static void AddTests(BTestSuite& suite);
private:
static status_t FindMessageWithProperty(
const char* property,
BMessage& validationFailuresMessage,
BMessage& validationFailureMessage);
static void FindValidationMessages(
BMessage& validationFailureMessage,
BStringList& validationMessages);
};
#endif // VALIDATION_FAILURE_TEST_H
@@ -0,0 +1,184 @@
/*
* Copyright 2019, Andrew Lindesay <apl@lindesay.co.nz>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
#include "ValidationUtilsTest.h"
#include <cppunit/TestCaller.h>
#include <cppunit/TestSuite.h>
#include "ValidationUtils.h"
ValidationUtilsTest::ValidationUtilsTest()
{
}
ValidationUtilsTest::~ValidationUtilsTest()
{
}
void
ValidationUtilsTest::TestEmailValid()
{
BString email("[email protected]");
// ----------------------
bool result = ValidationUtils::IsValidEmail(email);
// ----------------------
CPPUNIT_ASSERT_EQUAL_MESSAGE("!Email valid", true, result);
}
void TestEmailInvalidNoAt();
void TestEmailInvalidNoMailbox();
void TestEmailInvalidNoDomain();
void TestEmailInvalidTwoAts();
void
ValidationUtilsTest::TestEmailInvalidNoAt()
{
BString email("wetaexample.com");
// ----------------------
bool result = ValidationUtils::IsValidEmail(email);
// ----------------------
CPPUNIT_ASSERT_EQUAL_MESSAGE("!Email invalid - no @", false, result);
}
void
ValidationUtilsTest::TestEmailInvalidNoMailbox()
{
BString email("@example.com");
// ----------------------
bool result = ValidationUtils::IsValidEmail(email);
// ----------------------
CPPUNIT_ASSERT_EQUAL_MESSAGE("!Email invalid - no mailbox", false, result);
}
void
ValidationUtilsTest::TestEmailInvalidNoDomain()
{
BString email("fredric@");
// ----------------------
bool result = ValidationUtils::IsValidEmail(email);
// ----------------------
CPPUNIT_ASSERT_EQUAL_MESSAGE("!Email invalid - no domain", false, result);
}
void
ValidationUtilsTest::TestNicknameValid()
{
BString nickname("erik55");
// ----------------------
bool result = ValidationUtils::IsValidNickname(nickname);
// ----------------------
CPPUNIT_ASSERT_EQUAL_MESSAGE("!Nickname valid", true, result);
}
void
ValidationUtilsTest::TestNicknameInvalid()
{
BString nickname("not a Nickname!");
// ----------------------
bool result = ValidationUtils::IsValidNickname(nickname);
// ----------------------
CPPUNIT_ASSERT_EQUAL_MESSAGE("!Nickname invalid", false, result);
}
void
ValidationUtilsTest::TestNicknameInvalidBadChars()
{
BString nickname("erik!!10");
// ----------------------
bool result = ValidationUtils::IsValidNickname(nickname);
// ----------------------
CPPUNIT_ASSERT_EQUAL_MESSAGE("!Nickname invalid (bad chars)",
false, result);
}
void
ValidationUtilsTest::TestPasswordClearValid()
{
BString passwordClear("P4NhelQoad4");
// ----------------------
bool result = ValidationUtils::IsValidPasswordClear(passwordClear);
// ----------------------
CPPUNIT_ASSERT_EQUAL_MESSAGE("!Password clear valid", true, result);
}
void
ValidationUtilsTest::TestPasswordClearInvalid()
{
BString passwordClear("only has lower case letters");
// needs some numbers / upper case characters in there too
// ----------------------
bool result = ValidationUtils::IsValidPasswordClear(passwordClear);
// ----------------------
CPPUNIT_ASSERT_EQUAL_MESSAGE("!Password clear invalid", false, result);
}
/*static*/ void
ValidationUtilsTest::AddTests(BTestSuite& parent)
{
CppUnit::TestSuite& suite = *new CppUnit::TestSuite(
"ValidationUtilsTest");
suite.addTest(
new CppUnit::TestCaller<ValidationUtilsTest>(
"ValidationUtilsTest::TestEmailInvalid",
&ValidationUtilsTest::TestEmailInvalid));
suite.addTest(
new CppUnit::TestCaller<ValidationUtilsTest>(
"ValidationUtilsTest::TestEmailValid",
&ValidationUtilsTest::TestEmailValid));
suite.addTest(
new CppUnit::TestCaller<ValidationUtilsTest>(
"ValidationUtilsTest::TestNicknameInvalid",
&ValidationUtilsTest::TestNicknameInvalid));
suite.addTest(
new CppUnit::TestCaller<ValidationUtilsTest>(
"ValidationUtilsTest::TestNicknameValid",
&ValidationUtilsTest::TestNicknameValid));
suite.addTest(
new CppUnit::TestCaller<ValidationUtilsTest>(
"ValidationUtilsTest::TestNicknameInvalidBadChars",
&ValidationUtilsTest::TestNicknameInvalidBadChars));
suite.addTest(
new CppUnit::TestCaller<ValidationUtilsTest>(
"ValidationUtilsTest::TestPasswordClearInvalid",
&ValidationUtilsTest::TestPasswordClearInvalid));
suite.addTest(
new CppUnit::TestCaller<ValidationUtilsTest>(
"ValidationUtilsTest::TestPasswordClearValid",
&ValidationUtilsTest::TestPasswordClearValid));
parent.addTest("ValidationUtilsTest", &suite);
}
@@ -0,0 +1,37 @@
/*
* Copyright 2019, Andrew Lindesay <apl@lindesay.co.nz>
* Distributed under the terms of the MIT License.
*/
#ifndef VALIDATION_UTILS_TEST_H
#define VALIDATION_UTILS_TEST_H
#include "Message.h"
#include <TestCase.h>
#include <TestSuite.h>
class ValidationUtilsTest : public CppUnit::TestCase {
public:
ValidationUtilsTest();
virtual ~ValidationUtilsTest();
void TestEmailValid();
void TestEmailInvalidNoAt();
void TestEmailInvalidNoMailbox();
void TestEmailInvalidNoDomain();
void TestEmailInvalidTwoAts();
void TestNicknameValid();
void TestNicknameInvalid();
void TestNicknameInvalidBadChars();
void TestPasswordClearValid();
void TestPasswordClearInvalid();
static void AddTests(BTestSuite& suite);
};
#endif // VALIDATION_UTILS_TEST_H