From ad6aa6c021672a0e5f4ac13397ae4fbcca91662b Mon Sep 17 00:00:00 2001 From: Igor Date: Tue, 11 Feb 2025 16:32:45 +0300 Subject: [PATCH 1/2] ALTER USER ... LOGIN resets fauled attempt count --- ydb/library/login/login.cpp | 11 +++++ ydb/library/login/login.h | 6 ++- ydb/library/login/login_ut.cpp | 84 ++++++++++++++++++++++------------ 3 files changed, 71 insertions(+), 30 deletions(-) diff --git a/ydb/library/login/login.cpp b/ydb/library/login/login.cpp index b04a7837701f..81c2d07c9af2 100644 --- a/ydb/library/login/login.cpp +++ b/ydb/library/login/login.cpp @@ -141,6 +141,10 @@ TLoginProvider::TBasicResponse TLoginProvider::ModifyUser(const TModifyUserReque if (request.CanLogin.has_value()) { user.IsEnabled = request.CanLogin.value(); + + if (user.IsEnabled && AccountLockout.AttemptThreshold != 0 && user.FailedLoginAttemptCount >= AccountLockout.AttemptThreshold) { + SidsShouldResetByUnbanning.insert(user.Name); + } } return response; @@ -354,6 +358,7 @@ bool TLoginProvider::CheckLockout(const TSidRecord& sid) const { void TLoginProvider::ResetFailedLoginAttemptCount(TSidRecord* sid) { sid->FailedLoginAttemptCount = 0; + SidsShouldResetByUnbanning.erase(sid->Name); } void TLoginProvider::UnlockAccount(TSidRecord* sid) { @@ -361,12 +366,18 @@ void TLoginProvider::UnlockAccount(TSidRecord* sid) { } bool TLoginProvider::ShouldResetFailedAttemptCount(const TSidRecord& sid) const { + if (SidsShouldResetByUnbanning.contains(sid.Name)) { + return true; + } + if (sid.FailedLoginAttemptCount == 0) { return false; } + if (AccountLockout.AttemptResetDuration == std::chrono::system_clock::duration::zero()) { return false; } + return sid.LastFailedLogin + AccountLockout.AttemptResetDuration < std::chrono::system_clock::now(); } diff --git a/ydb/library/login/login.h b/ydb/library/login/login.h index 42fa25c16255..2d8f8319bcae 100644 --- a/ydb/library/login/login.h +++ b/ydb/library/login/login.h @@ -222,8 +222,8 @@ class TLoginProvider { static bool CheckAllowedName(const TString& name); bool CheckLockout(const TSidRecord& sid) const; - static void ResetFailedLoginAttemptCount(TSidRecord* sid); - static void UnlockAccount(TSidRecord* sid); + void ResetFailedLoginAttemptCount(TSidRecord* sid); + void UnlockAccount(TSidRecord* sid); bool ShouldResetFailedAttemptCount(const TSidRecord& sid) const; bool ShouldUnlockAccount(const TSidRecord& sid) const; bool CheckPasswordOrHash(bool IsHashedPassword, const TString& user, const TString& password, TString& error) const; @@ -231,6 +231,8 @@ class TLoginProvider { struct TImpl; THolder Impl; + std::unordered_set SidsShouldResetByUnbanning; + TPasswordChecker PasswordChecker; THashChecker HashChecker; TAccountLockout AccountLockout; diff --git a/ydb/library/login/login_ut.cpp b/ydb/library/login/login_ut.cpp index 310e1a8458b4..6574a081b04e 100644 --- a/ydb/library/login/login_ut.cpp +++ b/ydb/library/login/login_ut.cpp @@ -474,63 +474,91 @@ Y_UNIT_TEST_SUITE(Login) { provider.Audience = "test_audience1"; provider.RotateKeys(); + TString userName = "user1"; + TString userPassword = "password1"; + TLoginProvider::TCreateUserRequest createUserRequest { - .User = "user1", - .Password = "password1" + .User = userName, + .Password = userPassword }; auto createUserResponse = provider.CreateUser(createUserRequest); UNIT_ASSERT(!createUserResponse.Error); + auto LoginWithWrongPassword = [&](TLoginProvider::TCheckLockOutResponse::EStatus checkLockoutStatus, int attempt) { + UNIT_ASSERT_VALUES_EQUAL(provider.IsLockedOut(provider.Sids[userName]), false); + auto checkLockoutResponse = provider.CheckLockOutUser({.User = userName}); + UNIT_ASSERT_EQUAL(checkLockoutResponse.Status, checkLockoutStatus); + auto loginUserResponse = provider.LoginUser({.User = userName, .Password = TStringBuilder() << "wrongpassword" << attempt}); + UNIT_ASSERT_EQUAL(loginUserResponse.Status, TLoginProvider::TLoginUserResponse::EStatus::INVALID_PASSWORD); + UNIT_ASSERT_VALUES_EQUAL(loginUserResponse.Error, "Invalid password"); + }; + { for (size_t attempt = 0; attempt < accountLockoutInitializer.AttemptThreshold - 1; attempt++) { - UNIT_ASSERT_VALUES_EQUAL(provider.IsLockedOut(provider.Sids[createUserRequest.User]), false); - auto checkLockoutResponse = provider.CheckLockOutUser({.User = createUserRequest.User}); - UNIT_ASSERT_EQUAL(checkLockoutResponse.Status, TLoginProvider::TCheckLockOutResponse::EStatus::UNLOCKED); - auto loginUserResponse = provider.LoginUser({.User = createUserRequest.User, .Password = TStringBuilder() << "wrongpassword" << attempt}); - UNIT_ASSERT_EQUAL(loginUserResponse.Status, TLoginProvider::TLoginUserResponse::EStatus::INVALID_PASSWORD); - UNIT_ASSERT_VALUES_EQUAL(loginUserResponse.Error, "Invalid password"); + LoginWithWrongPassword(TLoginProvider::TCheckLockOutResponse::EStatus::UNLOCKED, attempt); } - UNIT_ASSERT_VALUES_EQUAL(provider.IsLockedOut(provider.Sids[createUserRequest.User]), false); - auto checkLockoutResponse = provider.CheckLockOutUser({.User = createUserRequest.User}); + + UNIT_ASSERT_VALUES_EQUAL(provider.IsLockedOut(provider.Sids[userName]), false); + auto checkLockoutResponse = provider.CheckLockOutUser({.User = userName}); UNIT_ASSERT_EQUAL(checkLockoutResponse.Status, TLoginProvider::TCheckLockOutResponse::EStatus::UNLOCKED); } Sleep(TDuration::Seconds(4)); { - UNIT_ASSERT_VALUES_EQUAL(provider.IsLockedOut(provider.Sids[createUserRequest.User]), false); - auto checkLockoutResponse = provider.CheckLockOutUser({.User = createUserRequest.User}); - UNIT_ASSERT_EQUAL(checkLockoutResponse.Status, TLoginProvider::TCheckLockOutResponse::EStatus::RESET); - auto loginUserResponse = provider.LoginUser({.User = createUserRequest.User, .Password = "wrongpassword1"}); - UNIT_ASSERT_EQUAL(loginUserResponse.Status, TLoginProvider::TLoginUserResponse::EStatus::INVALID_PASSWORD); - UNIT_ASSERT_VALUES_EQUAL(loginUserResponse.Error, "Invalid password"); + LoginWithWrongPassword(TLoginProvider::TCheckLockOutResponse::EStatus::RESET, 1); } { for (size_t attempt = 0; attempt < accountLockoutInitializer.AttemptThreshold - 2; attempt++) { - UNIT_ASSERT_VALUES_EQUAL(provider.IsLockedOut(provider.Sids[createUserRequest.User]), false); - auto checkLockoutResponse = provider.CheckLockOutUser({.User = createUserRequest.User}); - UNIT_ASSERT_EQUAL(checkLockoutResponse.Status, TLoginProvider::TCheckLockOutResponse::EStatus::UNLOCKED); - auto loginUserResponse = provider.LoginUser({.User = createUserRequest.User, .Password = TStringBuilder() << "wrongpassword1" << attempt}); - UNIT_ASSERT_EQUAL(loginUserResponse.Status, TLoginProvider::TLoginUserResponse::EStatus::INVALID_PASSWORD); - UNIT_ASSERT_VALUES_EQUAL(loginUserResponse.Error, "Invalid password"); + LoginWithWrongPassword(TLoginProvider::TCheckLockOutResponse::EStatus::UNLOCKED, attempt); } - UNIT_ASSERT_VALUES_EQUAL(provider.IsLockedOut(provider.Sids[createUserRequest.User]), false); - auto checkLockoutResponse = provider.CheckLockOutUser({.User = createUserRequest.User}); + + UNIT_ASSERT_VALUES_EQUAL(provider.IsLockedOut(provider.Sids[userName]), false); + auto checkLockoutResponse = provider.CheckLockOutUser({.User = userName}); UNIT_ASSERT_EQUAL(checkLockoutResponse.Status, TLoginProvider::TCheckLockOutResponse::EStatus::UNLOCKED); } { - UNIT_ASSERT_VALUES_EQUAL(provider.IsLockedOut(provider.Sids[createUserRequest.User]), false); - auto checkLockoutResponse = provider.CheckLockOutUser({.User = createUserRequest.User}); + UNIT_ASSERT_VALUES_EQUAL(provider.IsLockedOut(provider.Sids[userName]), false); + auto checkLockoutResponse = provider.CheckLockOutUser({.User = userName}); UNIT_ASSERT_EQUAL(checkLockoutResponse.Status, TLoginProvider::TCheckLockOutResponse::EStatus::UNLOCKED); - auto loginUserResponse = provider.LoginUser({.User = createUserRequest.User, .Password = createUserRequest.Password}); + auto loginUserResponse = provider.LoginUser({.User = userName, .Password = userPassword}); UNIT_ASSERT_EQUAL(loginUserResponse.Status, TLoginProvider::TLoginUserResponse::EStatus::SUCCESS); UNIT_ASSERT_VALUES_EQUAL(loginUserResponse.Error, ""); auto validateTokenResponse = provider.ValidateToken({.Token = loginUserResponse.Token}); UNIT_ASSERT_VALUES_EQUAL(validateTokenResponse.Error, ""); - UNIT_ASSERT(validateTokenResponse.User == createUserRequest.User); + UNIT_ASSERT(validateTokenResponse.User == userName); + } + + Sleep(TDuration::Seconds(4)); + + { + for (size_t attempt = 0; attempt < accountLockoutInitializer.AttemptThreshold; attempt++) { + LoginWithWrongPassword(TLoginProvider::TCheckLockOutResponse::EStatus::UNLOCKED, attempt); + } + + { + UNIT_ASSERT_VALUES_EQUAL(provider.IsLockedOut(provider.Sids[userName]), true); + auto checkLockoutResponse = provider.CheckLockOutUser({.User = userName}); + UNIT_ASSERT_EQUAL(checkLockoutResponse.Status, TLoginProvider::TCheckLockOutResponse::EStatus::SUCCESS); + UNIT_ASSERT_STRING_CONTAINS(checkLockoutResponse.Error, TStringBuilder() << "User " << userName << " is not permitted to log in"); + } + + { + TLoginProvider::TModifyUserRequest alterRequest; + alterRequest.User = userName; + alterRequest.CanLogin = true; + auto alterResponse = provider.ModifyUser(alterRequest); + UNIT_ASSERT(!alterResponse.Error); + } + + { + UNIT_ASSERT_VALUES_EQUAL(provider.IsLockedOut(provider.Sids[userName]), false); + auto checkLockoutResponse = provider.CheckLockOutUser({.User = userName}); + UNIT_ASSERT_EQUAL(checkLockoutResponse.Status, TLoginProvider::TCheckLockOutResponse::EStatus::RESET); + } } } From 5a5ed5ce491475d90ed0a98a9d33bf315ed77b57 Mon Sep 17 00:00:00 2001 From: Igor Date: Wed, 12 Feb 2025 13:52:09 +0300 Subject: [PATCH 2/2] fix Andrey`s issues --- .../schemeshard__operation_alter_login.cpp | 3 +- ydb/core/tx/schemeshard/ut_login/ut_login.cpp | 52 ++++++++ ydb/library/login/login.cpp | 16 +-- ydb/library/login/login.h | 7 +- ydb/library/login/login_ut.cpp | 120 ++++++++++-------- 5 files changed, 134 insertions(+), 64 deletions(-) diff --git a/ydb/core/tx/schemeshard/schemeshard__operation_alter_login.cpp b/ydb/core/tx/schemeshard/schemeshard__operation_alter_login.cpp index 1c37bddba91d..4a2e4f4bd320 100644 --- a/ydb/core/tx/schemeshard/schemeshard__operation_alter_login.cpp +++ b/ydb/core/tx/schemeshard/schemeshard__operation_alter_login.cpp @@ -88,7 +88,8 @@ class TAlterLogin: public TSubOperationBase { auto& sid = context.SS->LoginProvider.Sids[modifyUser.GetUser()]; db.Table().Key(sid.Name).Update(sid.Type, sid.PasswordHash, sid.IsEnabled); + Schema::LoginSids::IsEnabled, + Schema::LoginSids::FailedAttemptCount>(sid.Type, sid.PasswordHash, sid.IsEnabled, sid.FailedLoginAttemptCount); result->SetStatus(NKikimrScheme::StatusSuccess); AddIsUserAdmin(modifyUser.GetUser(), context.SS->LoginProvider, additionalParts); diff --git a/ydb/core/tx/schemeshard/ut_login/ut_login.cpp b/ydb/core/tx/schemeshard/ut_login/ut_login.cpp index ded24b5b6167..3957bff01f85 100644 --- a/ydb/core/tx/schemeshard/ut_login/ut_login.cpp +++ b/ydb/core/tx/schemeshard/ut_login/ut_login.cpp @@ -1092,6 +1092,58 @@ Y_UNIT_TEST_SUITE(TSchemeShardLoginTest) { CheckToken(resultLogin.token(), describe, "user1"); } } + + Y_UNIT_TEST(ResetFailedAttemptCountAfterModifyUser) { + TTestBasicRuntime runtime; + runtime.AddAppDataInit([] (ui32 nodeIdx, NKikimr::TAppData& appData) { + Y_UNUSED(nodeIdx); + auto accountLockout = appData.AuthConfig.MutableAccountLockout(); + accountLockout->SetAttemptThreshold(4); + accountLockout->SetAttemptResetDuration("3s"); + }); + + TTestEnv env(runtime); + auto accountLockoutConfig = runtime.GetAppData().AuthConfig.GetAccountLockout(); + ui64 txId = 100; + { + auto describe = DescribePath(runtime, TTestTxConfig::SchemeShard, "/MyRoot"); + CheckSecurityState(describe, {.PublicKeysSize = 0, .SidsSize = 0}); + } + + TString userName = "user1"; + TString userPassword = "password1"; + + auto blockUser = [&]() { + for (size_t attempt = 0; attempt < accountLockoutConfig.GetAttemptThreshold(); attempt++) { + auto resultLogin = Login(runtime, userName, TStringBuilder() << "wrongpassword" << attempt); + UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), "Invalid password"); + } + }; + + auto loginUser = [&](TString error) { + auto resultLogin = Login(runtime, userName, userPassword); + UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), error); + }; + + auto reboot = [&]() { + TActorId sender = runtime.AllocateEdgeActor(); + RebootTablet(runtime, TTestTxConfig::SchemeShard, sender); + }; + + CreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", userName, userPassword); + + blockUser(); + loginUser(TStringBuilder() << "User " << userName << " is not permitted to log in"); + reboot(); + loginUser(TStringBuilder() << "User " << userName << " is not permitted to log in"); + ChangeIsEnabledUser(runtime, ++txId, "/MyRoot", userName, true); + loginUser(""); + + blockUser(); + ChangeIsEnabledUser(runtime, ++txId, "/MyRoot", userName, true); + reboot(); + loginUser(""); + } } namespace NSchemeShardUT_Private { diff --git a/ydb/library/login/login.cpp b/ydb/library/login/login.cpp index 81c2d07c9af2..508bf11e0a00 100644 --- a/ydb/library/login/login.cpp +++ b/ydb/library/login/login.cpp @@ -142,8 +142,8 @@ TLoginProvider::TBasicResponse TLoginProvider::ModifyUser(const TModifyUserReque if (request.CanLogin.has_value()) { user.IsEnabled = request.CanLogin.value(); - if (user.IsEnabled && AccountLockout.AttemptThreshold != 0 && user.FailedLoginAttemptCount >= AccountLockout.AttemptThreshold) { - SidsShouldResetByUnbanning.insert(user.Name); + if (user.IsEnabled && CheckLockoutByAttemptCount(user)) { + ResetFailedLoginAttemptCount(&user); } } @@ -351,14 +351,16 @@ std::vector TLoginProvider::GetGroupsMembership(const TString& member) return groups; } +bool TLoginProvider::CheckLockoutByAttemptCount(const TSidRecord& sid) const { + return AccountLockout.AttemptThreshold != 0 && sid.FailedLoginAttemptCount >= AccountLockout.AttemptThreshold; +} + bool TLoginProvider::CheckLockout(const TSidRecord& sid) const { - return !sid.IsEnabled - || AccountLockout.AttemptThreshold != 0 && sid.FailedLoginAttemptCount >= AccountLockout.AttemptThreshold; + return !sid.IsEnabled || CheckLockoutByAttemptCount(sid); } void TLoginProvider::ResetFailedLoginAttemptCount(TSidRecord* sid) { sid->FailedLoginAttemptCount = 0; - SidsShouldResetByUnbanning.erase(sid->Name); } void TLoginProvider::UnlockAccount(TSidRecord* sid) { @@ -366,10 +368,6 @@ void TLoginProvider::UnlockAccount(TSidRecord* sid) { } bool TLoginProvider::ShouldResetFailedAttemptCount(const TSidRecord& sid) const { - if (SidsShouldResetByUnbanning.contains(sid.Name)) { - return true; - } - if (sid.FailedLoginAttemptCount == 0) { return false; } diff --git a/ydb/library/login/login.h b/ydb/library/login/login.h index 2d8f8319bcae..acee71321961 100644 --- a/ydb/library/login/login.h +++ b/ydb/library/login/login.h @@ -221,9 +221,10 @@ class TLoginProvider { bool CheckSubjectExists(const TString& name, const ESidType::SidType& type); static bool CheckAllowedName(const TString& name); + bool CheckLockoutByAttemptCount(const TSidRecord& sid) const; bool CheckLockout(const TSidRecord& sid) const; - void ResetFailedLoginAttemptCount(TSidRecord* sid); - void UnlockAccount(TSidRecord* sid); + static void ResetFailedLoginAttemptCount(TSidRecord* sid); + static void UnlockAccount(TSidRecord* sid); bool ShouldResetFailedAttemptCount(const TSidRecord& sid) const; bool ShouldUnlockAccount(const TSidRecord& sid) const; bool CheckPasswordOrHash(bool IsHashedPassword, const TString& user, const TString& password, TString& error) const; @@ -231,8 +232,6 @@ class TLoginProvider { struct TImpl; THolder Impl; - std::unordered_set SidsShouldResetByUnbanning; - TPasswordChecker PasswordChecker; THashChecker HashChecker; TAccountLockout AccountLockout; diff --git a/ydb/library/login/login_ut.cpp b/ydb/library/login/login_ut.cpp index 6574a081b04e..2c158e17c61d 100644 --- a/ydb/library/login/login_ut.cpp +++ b/ydb/library/login/login_ut.cpp @@ -474,91 +474,111 @@ Y_UNIT_TEST_SUITE(Login) { provider.Audience = "test_audience1"; provider.RotateKeys(); - TString userName = "user1"; - TString userPassword = "password1"; - TLoginProvider::TCreateUserRequest createUserRequest { - .User = userName, - .Password = userPassword + .User = "user1", + .Password = "password1" }; auto createUserResponse = provider.CreateUser(createUserRequest); UNIT_ASSERT(!createUserResponse.Error); - auto LoginWithWrongPassword = [&](TLoginProvider::TCheckLockOutResponse::EStatus checkLockoutStatus, int attempt) { - UNIT_ASSERT_VALUES_EQUAL(provider.IsLockedOut(provider.Sids[userName]), false); - auto checkLockoutResponse = provider.CheckLockOutUser({.User = userName}); - UNIT_ASSERT_EQUAL(checkLockoutResponse.Status, checkLockoutStatus); - auto loginUserResponse = provider.LoginUser({.User = userName, .Password = TStringBuilder() << "wrongpassword" << attempt}); - UNIT_ASSERT_EQUAL(loginUserResponse.Status, TLoginProvider::TLoginUserResponse::EStatus::INVALID_PASSWORD); - UNIT_ASSERT_VALUES_EQUAL(loginUserResponse.Error, "Invalid password"); - }; - { for (size_t attempt = 0; attempt < accountLockoutInitializer.AttemptThreshold - 1; attempt++) { - LoginWithWrongPassword(TLoginProvider::TCheckLockOutResponse::EStatus::UNLOCKED, attempt); + UNIT_ASSERT_VALUES_EQUAL(provider.IsLockedOut(provider.Sids[createUserRequest.User]), false); + auto checkLockoutResponse = provider.CheckLockOutUser({.User = createUserRequest.User}); + UNIT_ASSERT_EQUAL(checkLockoutResponse.Status, TLoginProvider::TCheckLockOutResponse::EStatus::UNLOCKED); + auto loginUserResponse = provider.LoginUser({.User = createUserRequest.User, .Password = TStringBuilder() << "wrongpassword" << attempt}); + UNIT_ASSERT_EQUAL(loginUserResponse.Status, TLoginProvider::TLoginUserResponse::EStatus::INVALID_PASSWORD); + UNIT_ASSERT_VALUES_EQUAL(loginUserResponse.Error, "Invalid password"); } - - UNIT_ASSERT_VALUES_EQUAL(provider.IsLockedOut(provider.Sids[userName]), false); - auto checkLockoutResponse = provider.CheckLockOutUser({.User = userName}); + UNIT_ASSERT_VALUES_EQUAL(provider.IsLockedOut(provider.Sids[createUserRequest.User]), false); + auto checkLockoutResponse = provider.CheckLockOutUser({.User = createUserRequest.User}); UNIT_ASSERT_EQUAL(checkLockoutResponse.Status, TLoginProvider::TCheckLockOutResponse::EStatus::UNLOCKED); } Sleep(TDuration::Seconds(4)); { - LoginWithWrongPassword(TLoginProvider::TCheckLockOutResponse::EStatus::RESET, 1); + UNIT_ASSERT_VALUES_EQUAL(provider.IsLockedOut(provider.Sids[createUserRequest.User]), false); + auto checkLockoutResponse = provider.CheckLockOutUser({.User = createUserRequest.User}); + UNIT_ASSERT_EQUAL(checkLockoutResponse.Status, TLoginProvider::TCheckLockOutResponse::EStatus::RESET); + auto loginUserResponse = provider.LoginUser({.User = createUserRequest.User, .Password = "wrongpassword1"}); + UNIT_ASSERT_EQUAL(loginUserResponse.Status, TLoginProvider::TLoginUserResponse::EStatus::INVALID_PASSWORD); + UNIT_ASSERT_VALUES_EQUAL(loginUserResponse.Error, "Invalid password"); } { for (size_t attempt = 0; attempt < accountLockoutInitializer.AttemptThreshold - 2; attempt++) { - LoginWithWrongPassword(TLoginProvider::TCheckLockOutResponse::EStatus::UNLOCKED, attempt); + UNIT_ASSERT_VALUES_EQUAL(provider.IsLockedOut(provider.Sids[createUserRequest.User]), false); + auto checkLockoutResponse = provider.CheckLockOutUser({.User = createUserRequest.User}); + UNIT_ASSERT_EQUAL(checkLockoutResponse.Status, TLoginProvider::TCheckLockOutResponse::EStatus::UNLOCKED); + auto loginUserResponse = provider.LoginUser({.User = createUserRequest.User, .Password = TStringBuilder() << "wrongpassword1" << attempt}); + UNIT_ASSERT_EQUAL(loginUserResponse.Status, TLoginProvider::TLoginUserResponse::EStatus::INVALID_PASSWORD); + UNIT_ASSERT_VALUES_EQUAL(loginUserResponse.Error, "Invalid password"); } - - UNIT_ASSERT_VALUES_EQUAL(provider.IsLockedOut(provider.Sids[userName]), false); - auto checkLockoutResponse = provider.CheckLockOutUser({.User = userName}); + UNIT_ASSERT_VALUES_EQUAL(provider.IsLockedOut(provider.Sids[createUserRequest.User]), false); + auto checkLockoutResponse = provider.CheckLockOutUser({.User = createUserRequest.User}); UNIT_ASSERT_EQUAL(checkLockoutResponse.Status, TLoginProvider::TCheckLockOutResponse::EStatus::UNLOCKED); } { - UNIT_ASSERT_VALUES_EQUAL(provider.IsLockedOut(provider.Sids[userName]), false); - auto checkLockoutResponse = provider.CheckLockOutUser({.User = userName}); + UNIT_ASSERT_VALUES_EQUAL(provider.IsLockedOut(provider.Sids[createUserRequest.User]), false); + auto checkLockoutResponse = provider.CheckLockOutUser({.User = createUserRequest.User}); UNIT_ASSERT_EQUAL(checkLockoutResponse.Status, TLoginProvider::TCheckLockOutResponse::EStatus::UNLOCKED); - auto loginUserResponse = provider.LoginUser({.User = userName, .Password = userPassword}); + auto loginUserResponse = provider.LoginUser({.User = createUserRequest.User, .Password = createUserRequest.Password}); UNIT_ASSERT_EQUAL(loginUserResponse.Status, TLoginProvider::TLoginUserResponse::EStatus::SUCCESS); UNIT_ASSERT_VALUES_EQUAL(loginUserResponse.Error, ""); auto validateTokenResponse = provider.ValidateToken({.Token = loginUserResponse.Token}); UNIT_ASSERT_VALUES_EQUAL(validateTokenResponse.Error, ""); - UNIT_ASSERT(validateTokenResponse.User == userName); + UNIT_ASSERT(validateTokenResponse.User == createUserRequest.User); } + } - Sleep(TDuration::Seconds(4)); + Y_UNIT_TEST(ResetFailedAttemptCountWithAlterUserLogin) { + TAccountLockout::TInitializer accountLockoutInitializer {.AttemptThreshold = 4, .AttemptResetDuration = "3s"}; + TLoginProvider provider(accountLockoutInitializer); + provider.Audience = "test_audience1"; + provider.RotateKeys(); - { - for (size_t attempt = 0; attempt < accountLockoutInitializer.AttemptThreshold; attempt++) { - LoginWithWrongPassword(TLoginProvider::TCheckLockOutResponse::EStatus::UNLOCKED, attempt); - } + TString userName = "user1"; + TString userPassword = "password1"; - { - UNIT_ASSERT_VALUES_EQUAL(provider.IsLockedOut(provider.Sids[userName]), true); - auto checkLockoutResponse = provider.CheckLockOutUser({.User = userName}); - UNIT_ASSERT_EQUAL(checkLockoutResponse.Status, TLoginProvider::TCheckLockOutResponse::EStatus::SUCCESS); - UNIT_ASSERT_STRING_CONTAINS(checkLockoutResponse.Error, TStringBuilder() << "User " << userName << " is not permitted to log in"); - } + TLoginProvider::TCreateUserRequest createUserRequest { + .User = userName, + .Password = userPassword + }; - { - TLoginProvider::TModifyUserRequest alterRequest; - alterRequest.User = userName; - alterRequest.CanLogin = true; - auto alterResponse = provider.ModifyUser(alterRequest); - UNIT_ASSERT(!alterResponse.Error); - } + auto createUserResponse = provider.CreateUser(createUserRequest); + UNIT_ASSERT(!createUserResponse.Error); - { - UNIT_ASSERT_VALUES_EQUAL(provider.IsLockedOut(provider.Sids[userName]), false); - auto checkLockoutResponse = provider.CheckLockOutUser({.User = userName}); - UNIT_ASSERT_EQUAL(checkLockoutResponse.Status, TLoginProvider::TCheckLockOutResponse::EStatus::RESET); - } + for (size_t attempt = 0; attempt < accountLockoutInitializer.AttemptThreshold; attempt++) { + UNIT_ASSERT_VALUES_EQUAL(provider.IsLockedOut(provider.Sids[userName]), false); + auto checkLockoutResponse = provider.CheckLockOutUser({.User = userName}); + UNIT_ASSERT_EQUAL(checkLockoutResponse.Status, TLoginProvider::TCheckLockOutResponse::EStatus::UNLOCKED); + auto loginUserResponse = provider.LoginUser({.User = userName, .Password = TStringBuilder() << "wrongpassword" << attempt}); + UNIT_ASSERT_EQUAL(loginUserResponse.Status, TLoginProvider::TLoginUserResponse::EStatus::INVALID_PASSWORD); + UNIT_ASSERT_VALUES_EQUAL(loginUserResponse.Error, "Invalid password"); + } + + { + UNIT_ASSERT_VALUES_EQUAL(provider.IsLockedOut(provider.Sids[userName]), true); + auto checkLockoutResponse = provider.CheckLockOutUser({.User = userName}); + UNIT_ASSERT_EQUAL(checkLockoutResponse.Status, TLoginProvider::TCheckLockOutResponse::EStatus::SUCCESS); + UNIT_ASSERT_STRING_CONTAINS(checkLockoutResponse.Error, TStringBuilder() << "User " << userName << " is not permitted to log in"); + } + + { + TLoginProvider::TModifyUserRequest alterRequest; + alterRequest.User = userName; + alterRequest.CanLogin = true; + auto alterResponse = provider.ModifyUser(alterRequest); + UNIT_ASSERT(!alterResponse.Error); + } + + { + UNIT_ASSERT_VALUES_EQUAL(provider.IsLockedOut(provider.Sids[userName]), false); + auto checkLockoutResponse = provider.CheckLockOutUser({.User = userName}); + UNIT_ASSERT_EQUAL(checkLockoutResponse.Status, TLoginProvider::TCheckLockOutResponse::EStatus::UNLOCKED); } }