HaikuDepot: Password Requirements

Obtains HDS's password requirements and displays
those to the user when creating a new account.

Relates to #13986

Change-Id: I1b76181e17bc03e9baf915dadcd3c45f9eb89b3f
Reviewed-on: https://review.haiku-os.org/c/haiku/+/6203
Tested-by: Automation <automation@haiku-os.org>
Reviewed-by: Jérôme Duval <jerome.duval@gmail.com>
Reviewed-by: Adrien Destugues <pulkomandy@pulkomandy.tk>
This commit is contained in:
Andrew Lindesay 2023-03-13 22:37:48 +13:00
parent 37d99b7eaa
commit b6356b9116
7 changed files with 328 additions and 14 deletions

View File

@ -140,6 +140,7 @@ local applicationSources =
PackageInfoListener.cpp
PackageInfoView.cpp
PackageListView.cpp
PasswordRequirements.cpp
RatePackageWindow.cpp
RatingView.cpp
RatingStability.cpp

View File

@ -0,0 +1,93 @@
/*
* Copyright 2023, Andrew Lindesay <apl@lindesay.co.nz>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
#include "PasswordRequirements.h"
// These are keys that are used to store this object's data into a BMessage
// instance.
#define KEY_MIN_PASSWORD_LENGTH "minPasswordLength"
#define KEY_MIN_PASSWORD_UPPERCASE_CHAR "minPasswordUppercaseChar"
#define KEY_MIN_PASSWORD_DIGITS_CHAR "minPasswordDigitsChar"
PasswordRequirements::PasswordRequirements()
:
fMinPasswordLength(0),
fMinPasswordUppercaseChar(0),
fMinPasswordDigitsChar(0)
{
}
PasswordRequirements::PasswordRequirements(BMessage* from)
:
fMinPasswordLength(0),
fMinPasswordUppercaseChar(0),
fMinPasswordDigitsChar(0)
{
uint32 value;
if (from->FindUInt32(KEY_MIN_PASSWORD_LENGTH, &value) == B_OK) {
fMinPasswordLength = value;
}
if (from->FindUInt32(
KEY_MIN_PASSWORD_UPPERCASE_CHAR, &value) == B_OK) {
fMinPasswordUppercaseChar = value;
}
if (from->FindUInt32(
KEY_MIN_PASSWORD_DIGITS_CHAR, &value) == B_OK) {
fMinPasswordDigitsChar = value;
}
}
PasswordRequirements::~PasswordRequirements()
{
}
void
PasswordRequirements::SetMinPasswordLength(uint32 value)
{
fMinPasswordLength = value;
}
void
PasswordRequirements::SetMinPasswordUppercaseChar(uint32 value)
{
fMinPasswordUppercaseChar = value;
}
void
PasswordRequirements::SetMinPasswordDigitsChar(uint32 value)
{
fMinPasswordDigitsChar = value;
}
status_t
PasswordRequirements::Archive(BMessage* into, bool deep) const
{
status_t result = B_OK;
if (result == B_OK) {
result = into->AddUInt32(
KEY_MIN_PASSWORD_LENGTH, fMinPasswordLength);
}
if (result == B_OK) {
result = into->AddUInt32(
KEY_MIN_PASSWORD_UPPERCASE_CHAR, fMinPasswordUppercaseChar);
}
if (result == B_OK) {
result = into->AddUInt32(
KEY_MIN_PASSWORD_DIGITS_CHAR, fMinPasswordDigitsChar);
}
return result;
}

View File

@ -0,0 +1,43 @@
/*
* Copyright 2023, Andrew Lindesay <apl@lindesay.co.nz>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
#ifndef PASSWORD_REQUIREMENTS_H
#define PASSWORD_REQUIREMENTS_H
#include <Archivable.h>
#include <String.h>
/*! When a user enters their password there are requirements around that
password such as the length of the password in characters as well as
how many digits it must contain. This class models those
requirements so that they can be conveyed to the user in the UI.
*/
class PasswordRequirements : public BArchivable {
public:
PasswordRequirements(BMessage* from);
PasswordRequirements();
virtual ~PasswordRequirements();
const uint32 MinPasswordLength() const
{ return fMinPasswordLength; }
const uint32 MinPasswordUppercaseChar() const
{ return fMinPasswordUppercaseChar; }
const uint32 MinPasswordDigitsChar() const
{ return fMinPasswordDigitsChar; }
void SetMinPasswordLength(uint32 value);
void SetMinPasswordUppercaseChar(uint32 value);
void SetMinPasswordDigitsChar(uint32 value);
status_t Archive(BMessage* into, bool deep = true) const;
private:
uint32 fMinPasswordLength;
uint32 fMinPasswordUppercaseChar;
uint32 fMinPasswordDigitsChar;
};
#endif // PASSWORD_REQUIREMENTS_H

View File

@ -1,6 +1,6 @@
/*
* Copyright 2014, Stephan Aßmus <superstippi@gmx.de>.
* Copyright 2016-2022, Andrew Lindesay <apl@lindesay.co.nz>.
* Copyright 2016-2023, Andrew Lindesay <apl@lindesay.co.nz>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
@ -741,6 +741,57 @@ WebAppInterface::IncrementViewCounter(const PackageInfoRef package,
}
status_t
WebAppInterface::RetrievePasswordRequirements(
PasswordRequirements& passwordRequirements)
{
BMessage responseEnvelopeMessage;
status_t result = _RetrievePasswordRequirementsMeta(
responseEnvelopeMessage);
if (result != B_OK)
return result;
BMessage resultMessage;
result = responseEnvelopeMessage.FindMessage("result", &resultMessage);
if (result != B_OK) {
HDERROR("bad response envelope missing 'result' entry");
return result;
}
double value;
if (resultMessage.FindDouble("minPasswordLength", &value) == B_OK)
passwordRequirements.SetMinPasswordLength((uint32) value);
if (resultMessage.FindDouble("minPasswordUppercaseChar", &value) == B_OK)
passwordRequirements.SetMinPasswordUppercaseChar((uint32) value);
if (resultMessage.FindDouble("minPasswordDigitsChar", &value) == B_OK)
passwordRequirements.SetMinPasswordDigitsChar((uint32) value);
return result;
}
status_t
WebAppInterface::_RetrievePasswordRequirementsMeta(BMessage& message)
{
BMallocIO* requestEnvelopeData = new BMallocIO();
// BHttpRequest later takes ownership of this.
BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData);
requestEnvelopeWriter.WriteObjectStart();
requestEnvelopeWriter.WriteObjectEnd();
return _SendJsonRequest("user/get-password-requirements",
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

View File

@ -1,6 +1,6 @@
/*
* Copyright 2014, Stephan Aßmus <superstippi@gmx.de>.
* Copyright 2016-2022, Andrew Lindesay <apl@lindesay.co.nz>.
* Copyright 2016-2023, Andrew Lindesay <apl@lindesay.co.nz>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
#ifndef WEB_APP_INTERFACE_H
@ -13,6 +13,7 @@
#include <package/PackageVersion.h>
#include "PackageInfo.h"
#include "PasswordRequirements.h"
#include "UserCredentials.h"
#include "UserDetail.h"
#include "UserUsageConditions.h"
@ -129,6 +130,9 @@ public:
const DepotInfoRef depot,
BMessage& message);
status_t RetrievePasswordRequirements(
PasswordRequirements& passwordRequirements);
static int32 ErrorCodeFromResponse(
BMessage& responseEnvelopeMessage);
@ -137,6 +141,8 @@ public:
UserDetail& userDetail);
private:
status_t _RetrievePasswordRequirementsMeta(
BMessage& message);
status_t _RetrieveUserUsageConditionsMeta(
const BString& code, BMessage& message);

View File

@ -1,6 +1,6 @@
/*
* Copyright 2014, Stephan Aßmus <superstippi@gmx.de>.
* Copyright 2019-2021, Andrew Lindesay <apl@lindesay.co.nz>.
* Copyright 2019-2023, Andrew Lindesay <apl@lindesay.co.nz>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
@ -47,6 +47,7 @@
#define KEY_USER_CREDENTIALS "userCredentials"
#define KEY_CAPTCHA_IMAGE "captchaImage"
#define KEY_USER_USAGE_CONDITIONS "userUsageConditions"
#define KEY_PASSWORD_REQUIREMENTS "passwordRequirements"
#define KEY_VALIDATION_FAILURES "validationFailures"
@ -67,7 +68,8 @@ enum {
MSG_LOGIN_ERROR = 'lter',
MSG_CREATE_ACCOUNT_SUCCESS = 'csuc',
MSG_CREATE_ACCOUNT_FAILED = 'cfai',
MSG_CREATE_ACCOUNT_ERROR = 'cfae'
MSG_CREATE_ACCOUNT_ERROR = 'cfae',
MSG_VIEW_PASSWORD_REQUIREMENTS = 'vpar'
};
@ -78,7 +80,8 @@ enum {
enum CreateAccountSetupMask {
CREATE_CAPTCHA = 1 << 1,
FETCH_USER_USAGE_CONDITIONS = 1 << 2
FETCH_USER_USAGE_CONDITIONS = 1 << 2,
FETCH_PASSWORD_REQUIREMENTS = 1 << 3
};
@ -121,6 +124,7 @@ UserLoginWindow::UserLoginWindow(BWindow* parent, BRect frame, Model& model)
B_FLOATING_WINDOW_LOOK, B_FLOATING_SUBSET_WINDOW_FEEL,
B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS
| B_NOT_RESIZABLE | B_NOT_ZOOMABLE | B_CLOSE_ON_ESCAPE),
fPasswordRequirements(NULL),
fUserUsageConditions(NULL),
fCaptcha(NULL),
fPreferredLanguageCode(LANGUAGE_DEFAULT_CODE),
@ -178,6 +182,10 @@ UserLoginWindow::UserLoginWindow(BWindow* parent, BRect frame, Model& model)
B_TRANSLATE("View the usage conditions"),
new BMessage(MSG_VIEW_LATEST_USER_USAGE_CONDITIONS));
fUserUsageConditionsLink->SetTarget(this);
fPasswordRequirementsLink = new LinkView("password requirements view",
B_TRANSLATE("View the password requirements"),
new BMessage(MSG_VIEW_PASSWORD_REQUIREMENTS));
fPasswordRequirementsLink->SetTarget(this);
// Setup modification messages on all text fields to trigger validation
// of input
@ -208,14 +216,15 @@ UserLoginWindow::UserLoginWindow(BWindow* parent, BRect frame, Model& model)
BLayoutBuilder::Grid<>(createAccountCard)
.AddTextControl(fNewNicknameField, 0, 0)
.AddTextControl(fNewPasswordField, 0, 1)
.AddTextControl(fRepeatPasswordField, 0, 2)
.AddTextControl(fEmailField, 0, 3)
.AddMenuField(fLanguageCodeField, 0, 4)
.Add(fCaptchaView, 0, 5)
.Add(fCaptchaResultField, 1, 5)
.Add(fConfirmMinimumAgeCheckBox, 1, 6)
.Add(fConfirmUserUsageConditionsCheckBox, 1, 7)
.Add(fUserUsageConditionsLink, 1, 8)
.Add(fPasswordRequirementsLink, 1, 2)
.AddTextControl(fRepeatPasswordField, 0, 3)
.AddTextControl(fEmailField, 0, 4)
.AddMenuField(fLanguageCodeField, 0, 5)
.Add(fCaptchaView, 0, 6)
.Add(fCaptchaResultField, 1, 6)
.Add(fConfirmMinimumAgeCheckBox, 1, 7)
.Add(fConfirmUserUsageConditionsCheckBox, 1, 8)
.Add(fUserUsageConditionsLink, 1, 9)
.SetInsets(B_USE_DEFAULT_SPACING)
;
fTabView->AddTab(createAccountCard);
@ -265,6 +274,10 @@ UserLoginWindow::MessageReceived(BMessage* message)
_ViewUserUsageConditions();
break;
case MSG_VIEW_PASSWORD_REQUIREMENTS:
_ViewPasswordRequirements();
break;
case MSG_SEND:
switch (fMode) {
case LOGIN:
@ -401,6 +414,7 @@ UserLoginWindow::_EnableMutableControls(bool enabled)
fConfirmMinimumAgeCheckBox->SetEnabled(enabled);
fConfirmUserUsageConditionsCheckBox->SetEnabled(enabled);
fUserUsageConditionsLink->SetEnabled(enabled);
fPasswordRequirementsLink->SetEnabled(enabled);
fSendButton->SetEnabled(enabled);
}
@ -667,6 +681,8 @@ UserLoginWindow::_CreateAccountSetupIfNecessary()
setupMask |= CREATE_CAPTCHA;
if (fUserUsageConditions == NULL)
setupMask |= FETCH_USER_USAGE_CONDITIONS;
if (fPasswordRequirements == NULL)
setupMask |= FETCH_PASSWORD_REQUIREMENTS;
_CreateAccountSetup(setupMask);
}
@ -695,6 +711,8 @@ UserLoginWindow::_CreateAccountSetup(uint32 mask)
_SetCaptcha(NULL);
if ((mask & FETCH_USER_USAGE_CONDITIONS) != 0)
_SetUserUsageConditions(NULL);
if ((mask & FETCH_PASSWORD_REQUIREMENTS) != 0)
_SetPasswordRequirements(NULL);
Unlock();
@ -722,9 +740,13 @@ UserLoginWindow::_CreateAccountSetupThreadEntry(void* data)
status_t result = B_OK;
Captcha captcha;
UserUsageConditions userUsageConditions;
PasswordRequirements passwordRequirements;
bool shouldCreateCaptcha = (threadData->mask & CREATE_CAPTCHA) != 0;
bool shouldFetchUserUsageConditions
= (threadData->mask & FETCH_USER_USAGE_CONDITIONS) != 0;
bool shouldFetchPasswordRequirements
= (threadData->mask & FETCH_PASSWORD_REQUIREMENTS) != 0;
if (result == B_OK && shouldCreateCaptcha)
result = threadData->window->_CreateAccountCaptchaSetupThread(captcha);
@ -732,6 +754,16 @@ UserLoginWindow::_CreateAccountSetupThreadEntry(void* data)
result = threadData->window
->_CreateAccountUserUsageConditionsSetupThread(userUsageConditions);
}
if (result == B_OK && shouldFetchPasswordRequirements) {
result = threadData->window
->_CreateAccountPasswordRequirementsSetupThread(
passwordRequirements);
HDINFO("password requirements fetched; len %" B_PRId32
", caps %" B_PRId32 ", digits %" B_PRId32,
passwordRequirements.MinPasswordLength(),
passwordRequirements.MinPasswordUppercaseChar(),
passwordRequirements.MinPasswordUppercaseChar());
}
if (result == B_OK) {
BMessage message(MSG_CREATE_ACCOUNT_SETUP_SUCCESS);
@ -749,6 +781,14 @@ UserLoginWindow::_CreateAccountSetupThreadEntry(void* data)
&userUsageConditionsMessage);
}
}
if (result == B_OK && shouldFetchPasswordRequirements) {
BMessage passwordRequirementsMessage;
result = passwordRequirements.Archive(&passwordRequirementsMessage);
if (result == B_OK) {
result = message.AddMessage(KEY_PASSWORD_REQUIREMENTS,
&passwordRequirementsMessage);
}
}
if (result == B_OK) {
HDDEBUG("successfully completed collection of create account "
"data from the server in background thread");
@ -792,6 +832,27 @@ UserLoginWindow::_CreateAccountUserUsageConditionsSetupThread(
}
status_t
UserLoginWindow::_CreateAccountPasswordRequirementsSetupThread(
PasswordRequirements& passwordRequirements)
{
WebAppInterface interface = fModel.GetWebAppInterface();
status_t result = interface.RetrievePasswordRequirements(
passwordRequirements);
if (result != B_OK) {
AppUtils::NotifySimpleError(
B_TRANSLATE("Password requirements download problem"),
B_TRANSLATE("An error has arisen downloading the password "
"requirements required to create a new user. Check the log for "
"details and try again. "
ALERT_MSG_LOGS_USER_GUIDE));
}
return result;
}
status_t
UserLoginWindow::_CreateAccountCaptchaSetupThread(Captcha& captcha)
{
@ -891,8 +952,10 @@ void
UserLoginWindow::_HandleCreateAccountSetupSuccess(BMessage* message)
{
HDDEBUG("handling account setup success");
BMessage captchaMessage;
BMessage userUsageConditionsMessage;
BMessage passwordRequirementsMessage;
if (message->FindMessage(KEY_CAPTCHA_IMAGE, &captchaMessage) == B_OK)
_SetCaptcha(new Captcha(&captchaMessage));
@ -903,6 +966,12 @@ UserLoginWindow::_HandleCreateAccountSetupSuccess(BMessage* message)
new UserUsageConditions(&userUsageConditionsMessage));
}
if (message->FindMessage(KEY_PASSWORD_REQUIREMENTS,
&passwordRequirementsMessage) == B_OK) {
_SetPasswordRequirements(
new PasswordRequirements(&passwordRequirementsMessage));
}
_EnableMutableControls(true);
}
@ -953,6 +1022,24 @@ UserLoginWindow::_SetUserUsageConditions(
}
void
UserLoginWindow::_SetPasswordRequirements(
PasswordRequirements* passwordRequirements)
{
HDDEBUG("setting password requirements");
if (fPasswordRequirements != NULL)
delete fPasswordRequirements;
fPasswordRequirements = passwordRequirements;
if (fPasswordRequirements != NULL) {
HDDEBUG("password requirements set to; len %" B_PRId32
", caps %" B_PRId32 ", digits %" B_PRId32,
fPasswordRequirements->MinPasswordLength(),
fPasswordRequirements->MinPasswordUppercaseChar(),
fPasswordRequirements->MinPasswordUppercaseChar());
}
}
// #pragma mark - Create Account
@ -1357,3 +1444,27 @@ UserLoginWindow::_ViewUserUsageConditions()
fModel, *fUserUsageConditions);
window->Show();
}
void
UserLoginWindow::_ViewPasswordRequirements()
{
if (fPasswordRequirements == NULL)
HDFATAL("the password requirements must have been setup");
BString msg = B_TRANSLATE("The password must be a minimum of "
"%MinPasswordLength% characters. "
"%MinPasswordUppercaseChar% characters must be upper-case and "
"%MinPasswordDigitsChar% characters must be digits.");
msg.ReplaceAll("%MinPasswordLength%",
BString() << fPasswordRequirements->MinPasswordLength());
msg.ReplaceAll("%MinPasswordUppercaseChar%",
BString() << fPasswordRequirements->MinPasswordUppercaseChar());
msg.ReplaceAll("%MinPasswordDigitsChar%",
BString() << fPasswordRequirements->MinPasswordDigitsChar());
BAlert* alert = new(std::nothrow) BAlert(
B_TRANSLATE("Password requirements"), msg, B_TRANSLATE("OK"));
if (alert != NULL)
alert->Go();
}

View File

@ -1,6 +1,6 @@
/*
* Copyright 2014, Stephan Aßmus <superstippi@gmx.de>.
* Copyright 2019, Andrew Lindesay <apl@lindesay.co.nz>.
* Copyright 2019-2023, Andrew Lindesay <apl@lindesay.co.nz>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
#ifndef USER_LOGIN_WINDOW_H
@ -12,6 +12,7 @@
#include "CreateUserDetail.h"
#include "PackageInfo.h"
#include "PasswordRequirements.h"
#include "UserCredentials.h"
#include "ValidationFailure.h"
@ -95,6 +96,8 @@ private:
Captcha& captcha);
status_t _CreateAccountUserUsageConditionsSetupThread(
UserUsageConditions& userUsageConditions);
status_t _CreateAccountPasswordRequirementsSetupThread(
PasswordRequirements& passwordRequirements);
status_t _UnpackCaptcha(BMessage& responsePayload,
Captcha& captcha);
void _HandleCreateAccountSetupSuccess(
@ -103,12 +106,15 @@ private:
void _SetCaptcha(Captcha* captcha);
void _SetUserUsageConditions(
UserUsageConditions* userUsageConditions);
void _SetPasswordRequirements(
PasswordRequirements* passwordRequirements);
void _CollectValidationFailures(
const BMessage& result,
BString& error) const;
void _ViewUserUsageConditions();
void _ViewPasswordRequirements();
void _TakeUpCredentialsAndQuit(
const UserCredentials& credentials);
@ -131,6 +137,7 @@ private:
BTextControl* fNewNicknameField;
BTextControl* fNewPasswordField;
BTextControl* fRepeatPasswordField;
LinkView* fPasswordRequirementsLink;
BTextControl* fEmailField;
BMenuField* fLanguageCodeField;
BitmapView* fCaptchaView;
@ -142,6 +149,8 @@ private:
BButton* fSendButton;
BButton* fCancelButton;
PasswordRequirements*
fPasswordRequirements;
UserUsageConditions*
fUserUsageConditions;
Captcha* fCaptcha;