Centralized demo account logic in the password auth service. ref #167

This commit is contained in:
emeric
2021-09-03 13:41:30 +02:00
parent a261484bb8
commit 7affe08fc4
15 changed files with 107 additions and 47 deletions
@@ -80,16 +80,16 @@ namespace Auth
return true;
}
bool
InternalPasswordService::isPasswordSecureEnough(std::string_view password, const PasswordValidationContext& context) const
IPasswordService::PasswordAcceptabilityResult
InternalPasswordService::checkPasswordAcceptability(std::string_view password, const PasswordValidationContext& context) const
{
switch (context.userType)
{
case Database::UserType::ADMIN:
case Database::UserType::REGULAR:
return _validator.evaluateStrength(std::string {password}, context.loginName, "").isValid();
return _validator.evaluateStrength(std::string {password}, context.loginName, "").isValid() ? PasswordAcceptabilityResult::OK : PasswordAcceptabilityResult::TooWeak;
case Database::UserType::DEMO:
return true; // no constraint
return password == context.loginName ? PasswordAcceptabilityResult::OK : PasswordAcceptabilityResult::MustMatchLoginName;
}
throw NotImplementedException {};
@@ -106,8 +106,15 @@ namespace Auth
if (!user)
throw Exception {"User not found!"};
if (!isPasswordSecureEnough(newPassword, PasswordValidationContext {user->getLoginName(), user->getType()} ))
throw PasswordTooWeakException {};
switch (checkPasswordAcceptability(newPassword, PasswordValidationContext {user->getLoginName(), user->getType()}))
{
case PasswordAcceptabilityResult::OK:
break;
case PasswordAcceptabilityResult::TooWeak:
throw PasswordTooWeakException {};
case PasswordAcceptabilityResult::MustMatchLoginName:
throw PasswordMustMatchLoginNameException {};
}
user.modify()->setPasswordHash(passwordHash);
getAuthTokenService().clearAuthTokens(session, userId);
@@ -41,7 +41,7 @@ namespace Auth
std::string_view password) override;
bool canSetPasswords() const override;
bool isPasswordSecureEnough(std::string_view loginName, const PasswordValidationContext& context) const override;
PasswordAcceptabilityResult checkPasswordAcceptability(std::string_view loginName, const PasswordValidationContext& context) const override;
void setPassword(Database::Session& session, Database::IdType userId, std::string_view newPassword) override;
Database::User::PasswordHash hashPassword(std::string_view password) const;
@@ -186,8 +186,8 @@ namespace Auth
return false;
}
bool
PAMPasswordService::isPasswordSecureEnough(std::string_view, const PasswordValidationContext&) const
IPasswordService::PasswordAcceptabilityResult
PAMPasswordService::checkPasswordAcceptability(std::string_view, const PasswordValidationContext&) const
{
throw NotImplementedException {};
}
@@ -36,7 +36,7 @@ namespace Auth
std::string_view password) override;
bool canSetPasswords() const override;
bool isPasswordSecureEnough(std::string_view loginName, const PasswordValidationContext& context) const override;
PasswordAcceptabilityResult checkPasswordAcceptability(std::string_view loginName, const PasswordValidationContext& context) const override;
void setPassword(Database::Session& session,
Database::IdType userId,
std::string_view newPassword) override;
@@ -56,15 +56,21 @@ namespace Auth
std::optional<Database::IdType> userId {};
std::optional<Wt::WDateTime> expiry {};
};
virtual CheckResult checkUserPassword(Database::Session& session,
const boost::asio::ip::address& clientAddress,
std::string_view loginName,
std::string_view password) = 0;
virtual CheckResult checkUserPassword(Database::Session& session,
const boost::asio::ip::address& clientAddress,
std::string_view loginName,
std::string_view password) = 0;
virtual bool canSetPasswords() const = 0;
virtual bool canSetPasswords() const = 0;
virtual bool isPasswordSecureEnough(std::string_view password, const PasswordValidationContext& context) const = 0;
virtual void setPassword(Database::Session& session, Database::IdType userId, std::string_view newPassword) = 0;
enum class PasswordAcceptabilityResult
{
OK,
TooWeak,
MustMatchLoginName,
};
virtual PasswordAcceptabilityResult checkPasswordAcceptability(std::string_view password, const PasswordValidationContext& context) const = 0;
virtual void setPassword(Database::Session& session, Database::IdType userId, std::string_view newPassword) = 0;
};
std::unique_ptr<IPasswordService> createPasswordService(std::string_view authPasswordBackend, std::size_t maxThrottlerEntryCount, IAuthTokenService& authTokenService);
+20 -2
View File
@@ -36,16 +36,34 @@ namespace Auth
NotImplementedException() : Auth::Exception {"Not implemented"} {}
};
class UserNotFoundException : public Exception
{
public:
UserNotFoundException() : Auth::Exception {"User not found"} {}
};
struct PasswordValidationContext
{
std::string loginName;
Database::UserType userType;
};
class PasswordTooWeakException : public Exception
class PasswordException : public Exception
{
public:
PasswordTooWeakException() : Auth::Exception {"Password too weak"} {}
using Exception::Exception;
};
class PasswordTooWeakException : public PasswordException
{
public:
PasswordTooWeakException() : PasswordException {"Password too weak"} {}
};
class PasswordMustMatchLoginNameException : public PasswordException
{
public:
PasswordMustMatchLoginNameException() : PasswordException {"Password must match login name"} {}
};
}
+15 -2
View File
@@ -559,11 +559,15 @@ handleChangePassword(RequestContext& context)
Service<Auth::IPasswordService>::get()->setPassword(context.dbSession, userId, password);
}
catch (Auth::PasswordTooWeakException&)
catch (const Auth::PasswordMustMatchLoginNameException&)
{
throw PasswordMustMatchLoginNameGenericError {};
}
catch (const Auth::PasswordTooWeakException&)
{
throw PasswordTooWeakGenericError {};
}
catch (Auth::Exception& authException)
catch (const Auth::Exception& authException)
{
throw UserNotAuthorizedError {};
}
@@ -658,6 +662,11 @@ handleCreateUserRequest(RequestContext& context)
{
Service<Auth::IPasswordService>::get()->setPassword(context.dbSession, userId, password);
}
catch (const Auth::PasswordMustMatchLoginNameException&)
{
removeCreatedUser();
throw PasswordMustMatchLoginNameGenericError {};
}
catch (const Auth::PasswordTooWeakException&)
{
removeCreatedUser();
@@ -1711,6 +1720,10 @@ handleUpdateUserRequest(RequestContext& context)
{
Service<::Auth::IPasswordService>()->setPassword(context.dbSession, userId, decodePasswordIfNeeded(*password));
}
catch (const Auth::PasswordMustMatchLoginNameException&)
{
throw PasswordMustMatchLoginNameGenericError {};
}
catch (const Auth::PasswordTooWeakException&)
{
throw PasswordTooWeakGenericError {};
@@ -152,6 +152,11 @@ class PasswordTooWeakGenericError : public GenericError
std::string getMessage() const override { return "Password too weak"; }
};
class PasswordMustMatchLoginNameGenericError : public GenericError
{
std::string getMessage() const override { return "Password must match login name"; }
};
class DemoUserCannotChangePasswordGenericError : public GenericError
{
std::string getMessage() const override { return "Demo user cannot change its password"; }
+8
View File
@@ -276,6 +276,14 @@ class SettingsModel : public Wt::WFormModel
validator(SettingsModel::ListenBrainzTokenField)->setMandatory(usesListenBrainz);
}
}
if (_authPasswordService)
{
if (_withOldPassword)
setValue(PasswordOldField, "");
setValue(PasswordField, "");
setValue(PasswordConfirmField, "");
}
}
private:
+15 -19
View File
@@ -66,7 +66,7 @@ class UserModel : public Wt::WFormModel
if (authPasswordService)
{
addField(PasswordField);
setValidator(PasswordField, createPasswordStrengthValidator([this] { return ::Auth::PasswordValidationContext {getLoginName(), Wt::asNumber(value(DemoField)) ? UserType::DEMO : UserType::REGULAR}; }));
setValidator(PasswordField, createPasswordStrengthValidator([this] { return ::Auth::PasswordValidationContext {getLoginName(), getUserType()}; }));
if (!userId)
validator(PasswordField)->setMandatory(true);
}
@@ -122,6 +122,19 @@ class UserModel : public Wt::WFormModel
throw UserNotAllowedException {};
}
Database::UserType getUserType() const
{
if (_userId)
{
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
const Database::User::pointer user {Database::User::getById(LmsApp->getDbSession(), *_userId)};
return user->getType();
}
return Wt::asNumber(value(DemoField)) ? UserType::DEMO : UserType::REGULAR;
}
std::string getLoginName() const
{
if (_userId)
@@ -131,18 +144,8 @@ class UserModel : public Wt::WFormModel
const Database::User::pointer user {Database::User::getById(LmsApp->getDbSession(), *_userId)};
return user->getLoginName();
}
else
return valueText(LoginField).toUTF8();
}
void validatePassword(Wt::WString& error) const
{
if (!valueText(PasswordField).empty() && Wt::asNumber(value(DemoField)))
{
// Demo account: password must be the same as the login name
if (valueText(PasswordField) != getLoginName())
error = Wt::WString::tr("Lms.Admin.User.demo-password-invalid");
}
return valueText(LoginField).toUTF8();
}
bool validateField(Field field)
@@ -157,13 +160,6 @@ class UserModel : public Wt::WFormModel
if (user)
error = Wt::WString::tr("Lms.Admin.User.user-already-exists");
}
else if (field == PasswordField)
{
if (Wt::asNumber(value(DemoField)))
setValidator(PasswordField, {});
validatePassword(error);
}
else if (field == DemoField)
{
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
+10 -3
View File
@@ -48,10 +48,17 @@ namespace UserInterface
const ::Auth::PasswordValidationContext context {_passwordValidationContextGetFunc()};
if (Service<::Auth::IPasswordService>::get()->isPasswordSecureEnough(input.toUTF8(), context))
return Wt::WValidator::Result {Wt::ValidationState::Valid};
switch (Service<::Auth::IPasswordService>::get()->checkPasswordAcceptability(input.toUTF8(), context))
{
case ::Auth::IPasswordService::PasswordAcceptabilityResult::OK:
return Wt::WValidator::Result {Wt::ValidationState::Valid};
case ::Auth::IPasswordService::PasswordAcceptabilityResult::TooWeak:
return Wt::WValidator::Result {Wt::ValidationState::Invalid, Wt::WString::tr("Lms.password-too-weak")};
case ::Auth::IPasswordService::PasswordAcceptabilityResult::MustMatchLoginName:
return Wt::WValidator::Result {Wt::ValidationState::Invalid, Wt::WString::tr("Lms.password-must-match-login")};
}
return Wt::WValidator::Result {Wt::ValidationState::Invalid, Wt::WString::tr("Lms.password-too-weak")};
throw LmsException {"internal error"};
}
std::shared_ptr<Wt::WValidator>