diff --git a/ydb/core/protos/counters_schemeshard.proto b/ydb/core/protos/counters_schemeshard.proto index 8dfdad08c6ec..5356608d47c5 100644 --- a/ydb/core/protos/counters_schemeshard.proto +++ b/ydb/core/protos/counters_schemeshard.proto @@ -645,4 +645,6 @@ enum ETxTypes { TXTYPE_ADD_SHARDS_DATA_ERASURE = 98 [(TxTypeOpts) = {Name: "TxAddShardsDataErasure"}]; TXTYPE_CANCEL_SHARDS_DATA_ERASURE = 99 [(TxTypeOpts) = {Name: "TxCancelShardsDataErasure"}]; + + TXTYPE_LOGIN_FINALIZE = 100 [(TxTypeOpts) = {Name: "TxLoginFinalize"}]; } diff --git a/ydb/core/tx/schemeshard/schemeshard__login.cpp b/ydb/core/tx/schemeshard/schemeshard__login.cpp index 797a78f4e7f7..d6d5c2ff239c 100644 --- a/ydb/core/tx/schemeshard/schemeshard__login.cpp +++ b/ydb/core/tx/schemeshard/schemeshard__login.cpp @@ -1,20 +1,20 @@ +#include "schemeshard_impl.h" +#include #include #include #include #include -#include "schemeshard_impl.h" - namespace NKikimr { namespace NSchemeShard { -using namespace NTabletFlatExecutor; - struct TSchemeShard::TTxLogin : TSchemeShard::TRwTxBase { TEvSchemeShard::TEvLogin::TPtr Request; + NLogin::TLoginProvider::TLoginUserResponse Response; TPathId SubDomainPathId; bool NeedPublishOnComplete = false; - THolder Result = MakeHolder(); + bool SendFinalizeEvent = false; + TString ErrMessage; TTxLogin(TSelf *self, TEvSchemeShard::TEvLogin::TPtr &ev) : TRwTxBase(self) @@ -43,35 +43,35 @@ struct TSchemeShard::TTxLogin : TSchemeShard::TRwTxBase { << " at schemeshard: " << Self->TabletID()); NIceDb::TNiceDb db(txc.DB); if (Self->LoginProvider.IsItTimeToRotateKeys()) { - LOG_DEBUG_S(ctx, NKikimrServices::FLAT_TX_SCHEMESHARD, "TTxLogin RotateKeys at schemeshard: " << Self->TabletID()); - std::vector keysExpired; - std::vector keysAdded; - Self->LoginProvider.RotateKeys(keysExpired, keysAdded); - SubDomainPathId = Self->GetCurrentSubDomainPathId(); - TSubDomainInfo::TPtr domainPtr = Self->ResolveDomainInfo(SubDomainPathId); - - // TODO(xenoxeno): optimize security state changes - domainPtr->UpdateSecurityState(Self->LoginProvider.GetSecurityState()); - domainPtr->IncSecurityStateVersion(); - - - Self->PersistSubDomainSecurityStateVersion(db, SubDomainPathId, *domainPtr); + RotateKeys(ctx, db); + NeedPublishOnComplete = true; + } - for (ui64 keyId : keysExpired) { - db.Table().Key(keyId).Delete(); - } - for (ui64 keyId : keysAdded) { - const auto* key = Self->LoginProvider.FindKey(keyId); - if (key) { - db.Table().Key(keyId).Update( - key->PublicKey, ToInstant(key->ExpiresAt).MilliSeconds()); - } + const auto& loginRequest = GetLoginRequest(); + if (!loginRequest.ExternalAuth) { + if (!AppData(ctx)->AuthConfig.GetEnableLoginAuthentication()) { + ErrMessage = "Login authentication is disabled"; + } else { + CheckLockOutUserAndSetErrorIfAny(loginRequest.User, db); } + } - NeedPublishOnComplete = true; + if (ErrMessage) { + SendError(); + return; } - LoginAttempt(db, ctx); + TString passwordHash; + if (Self->LoginProvider.NeedVerifyHash(loginRequest, &Response, &passwordHash)) { + ctx.Send( + Self->LoginHelper, + MakeHolder(loginRequest, Response, Request->Sender, passwordHash), + 0, + Request->Cookie + ); + } else { + SendFinalizeEvent = true; + } } void DoComplete(const TActorContext &ctx) override { @@ -79,65 +79,61 @@ struct TSchemeShard::TTxLogin : TSchemeShard::TRwTxBase { Self->PublishToSchemeBoard(TTxId(), {SubDomainPathId}, ctx); } + if (SendFinalizeEvent) { + auto event = MakeHolder( + GetLoginRequest(), Response, Request->Sender, "", /*needUpdateCache*/ false + ); + TEvPrivate::TEvLoginFinalize::TPtr eventPtr = (TEventHandle*) new IEventHandle( + Self->SelfId(), Self->SelfId(), event.Release() + ); + Self->Execute(Self->CreateTxLoginFinalize(eventPtr), ctx); + } + LOG_DEBUG_S(ctx, NKikimrServices::FLAT_TX_SCHEMESHARD, "TTxLogin Complete" - << ", result: " << Result->Record.ShortDebugString() + << ", with " << (ErrMessage ? "error: " + ErrMessage : "no errors") << ", at schemeshard: " << Self->TabletID()); - - ctx.Send(Request->Sender, std::move(Result), 0, Request->Cookie); - } +} private: - bool IsAdmin() const { - const auto& user = Request->Get()->Record.GetUser(); - const auto userToken = NKikimr::BuildLocalUserToken(Self->LoginProvider, user); - return IsAdministrator(AppData(), &userToken); - } - - void LoginAttempt(NIceDb::TNiceDb& db, const TActorContext& ctx) { - const auto& loginRequest = GetLoginRequest(); - if (!loginRequest.ExternalAuth && !AppData(ctx)->AuthConfig.GetEnableLoginAuthentication()) { - Result->Record.SetError("Login authentication is disabled"); - return; - } - if (loginRequest.ExternalAuth) { - HandleExternalAuth(loginRequest); - } else { - HandleLoginAuth(loginRequest, db); - } - } - - void HandleExternalAuth(const NLogin::TLoginProvider::TLoginUserRequest& loginRequest) { - const NLogin::TLoginProvider::TLoginUserResponse loginResponse = Self->LoginProvider.LoginUser(loginRequest); - switch (loginResponse.Status) { - case NLogin::TLoginProvider::TLoginUserResponse::EStatus::SUCCESS: { - Result->Record.SetToken(loginResponse.Token); - Result->Record.SetSanitizedToken(loginResponse.SanitizedToken); - Result->Record.SetIsAdmin(IsAdmin()); - break; - } - case NLogin::TLoginProvider::TLoginUserResponse::EStatus::INVALID_PASSWORD: - case NLogin::TLoginProvider::TLoginUserResponse::EStatus::INVALID_USER: - case NLogin::TLoginProvider::TLoginUserResponse::EStatus::UNAVAILABLE_KEY: - case NLogin::TLoginProvider::TLoginUserResponse::EStatus::UNSPECIFIED: { - Result->Record.SetError(loginResponse.Error); - break; + void RotateKeys(const TActorContext& ctx, NIceDb::TNiceDb& db) { + LOG_DEBUG_S(ctx, NKikimrServices::FLAT_TX_SCHEMESHARD, "TTxLogin RotateKeys at schemeshard: " << Self->TabletID()); + std::vector keysExpired; + std::vector keysAdded; + Self->LoginProvider.RotateKeys(keysExpired, keysAdded); + SubDomainPathId = Self->GetCurrentSubDomainPathId(); + TSubDomainInfo::TPtr domainPtr = Self->ResolveDomainInfo(SubDomainPathId); + + // TODO(xenoxeno): optimize security state changes + domainPtr->UpdateSecurityState(Self->LoginProvider.GetSecurityState()); + domainPtr->IncSecurityStateVersion(); + + Self->PersistSubDomainSecurityStateVersion(db, SubDomainPathId, *domainPtr); + + for (ui64 keyId : keysExpired) { + db.Table().Key(keyId).Delete(); } + for (ui64 keyId : keysAdded) { + const auto* key = Self->LoginProvider.FindKey(keyId); + if (key) { + db.Table().Key(keyId).Update( + key->PublicKey, ToInstant(key->ExpiresAt).MilliSeconds()); + } } } - void HandleLoginAuth(const NLogin::TLoginProvider::TLoginUserRequest& loginRequest, NIceDb::TNiceDb& db) { + void CheckLockOutUserAndSetErrorIfAny(const TString& user, NIceDb::TNiceDb& db) { using namespace NLogin; - const TLoginProvider::TCheckLockOutResponse checkLockOutResponse = Self->LoginProvider.CheckLockOutUser({.User = loginRequest.User}); + const TLoginProvider::TCheckLockOutResponse checkLockOutResponse = Self->LoginProvider.CheckLockOutUser({.User = user}); switch (checkLockOutResponse.Status) { case TLoginProvider::TCheckLockOutResponse::EStatus::SUCCESS: case TLoginProvider::TCheckLockOutResponse::EStatus::INVALID_USER: { - Result->Record.SetError(checkLockOutResponse.Error); + ErrMessage = checkLockOutResponse.Error; return; } case TLoginProvider::TCheckLockOutResponse::EStatus::RESET: { - const auto& sid = Self->LoginProvider.Sids[loginRequest.User]; - db.Table().Key(loginRequest.User).Update(sid.FailedLoginAttemptCount); + const auto& sid = Self->LoginProvider.Sids[user]; + db.Table().Key(user).Update(sid.FailedLoginAttemptCount); break; } case TLoginProvider::TCheckLockOutResponse::EStatus::UNLOCKED: @@ -145,32 +141,17 @@ struct TSchemeShard::TTxLogin : TSchemeShard::TRwTxBase { break; } } + } - const TLoginProvider::TLoginUserResponse loginResponse = Self->LoginProvider.LoginUser(loginRequest); - switch (loginResponse.Status) { - case TLoginProvider::TLoginUserResponse::EStatus::SUCCESS: { - const auto& sid = Self->LoginProvider.Sids[loginRequest.User]; - db.Table().Key(loginRequest.User).Update(ToMicroSeconds(sid.LastSuccessfulLogin), sid.FailedLoginAttemptCount); - Result->Record.SetToken(loginResponse.Token); - Result->Record.SetSanitizedToken(loginResponse.SanitizedToken); - Result->Record.SetIsAdmin(IsAdmin()); - break; - } - case TLoginProvider::TLoginUserResponse::EStatus::INVALID_PASSWORD: { - const auto& sid = Self->LoginProvider.Sids[loginRequest.User]; - db.Table().Key(loginRequest.User).Update(ToMicroSeconds(sid.LastFailedLogin), sid.FailedLoginAttemptCount); - Result->Record.SetError(loginResponse.Error); - break; - } - case NLogin::TLoginProvider::TLoginUserResponse::EStatus::INVALID_USER: - case NLogin::TLoginProvider::TLoginUserResponse::EStatus::UNAVAILABLE_KEY: - case NLogin::TLoginProvider::TLoginUserResponse::EStatus::UNSPECIFIED: { - Result->Record.SetError(loginResponse.Error); - break; - } - } + void SendError() { + THolder result = MakeHolder(); + result->Record.SetError(ErrMessage); + Self->Send( + Request->Sender, + std::move(result), + 0, + Request->Cookie + ); } }; diff --git a/ydb/core/tx/schemeshard/schemeshard__login_finalize.cpp b/ydb/core/tx/schemeshard/schemeshard__login_finalize.cpp new file mode 100644 index 000000000000..ad1aaaad7fea --- /dev/null +++ b/ydb/core/tx/schemeshard/schemeshard__login_finalize.cpp @@ -0,0 +1,132 @@ +#include "schemeshard_impl.h" +#include +#include +#include +#include + +namespace NKikimr { +namespace NSchemeShard { + +struct TSchemeShard::TTxLoginFinalize : TSchemeShard::TRwTxBase { +private: + TEvPrivate::TEvLoginFinalize::TPtr LoginFinalizeEventPtr; + TString ErrMessage; + +public: + TTxLoginFinalize(TSelf *self, TEvPrivate::TEvLoginFinalize::TPtr &ev) + : TRwTxBase(self) + , LoginFinalizeEventPtr(std::move(ev)) + {} + + TTxType GetTxType() const override { + return TXTYPE_LOGIN_FINALIZE; + } + + void DoExecute(TTransactionContext& txc, const TActorContext& ctx) override { + LOG_DEBUG_S(ctx, NKikimrServices::FLAT_TX_SCHEMESHARD, + "TTxLoginFinalize Execute" + << " at schemeshard: " << Self->TabletID()); + + const auto& event = *LoginFinalizeEventPtr->Get(); + if (event.NeedUpdateCache) { + const auto isSuccessVerifying = + event.CheckResult.Status == NLogin::TLoginProvider::TLoginUserResponse::EStatus::SUCCESS; + Self->LoginProvider.UpdateCache( + event.Request, + event.PasswordHash, + isSuccessVerifying + ); + } + const auto response = Self->LoginProvider.LoginUser(event.Request, event.CheckResult); + + if (!LoginFinalizeEventPtr->Get()->Request.ExternalAuth) { + UpdateLoginSidsStats(response, txc); + } + if (!response.Error.empty()) { + ErrMessage = response.Error; + SendError(response.Error); + return; + } + FillResult(response); + } + + void DoComplete(const TActorContext &ctx) override { + LOG_DEBUG_S(ctx, NKikimrServices::FLAT_TX_SCHEMESHARD, + "TTxLoginFinalize Completed" + << ", with " << (ErrMessage ? "error: " + ErrMessage : "no errors") + << " at schemeshard: " << Self->TabletID()); + } + +private: + bool IsAdmin(const TString& user) const { + const auto userToken = NKikimr::BuildLocalUserToken(Self->LoginProvider, user); + return IsAdministrator(AppData(), &userToken); + } + + void FillResult(const NLogin::TLoginProvider::TLoginUserResponse& response) { + THolder result = MakeHolder(); + switch (response.Status) { + case NLogin::TLoginProvider::TLoginUserResponse::EStatus::SUCCESS: { + result->Record.SetToken(response.Token); + result->Record.SetSanitizedToken(response.SanitizedToken); + result->Record.SetIsAdmin(IsAdmin(LoginFinalizeEventPtr->Get()->Request.User)); + break; + } + case NLogin::TLoginProvider::TLoginUserResponse::EStatus::INVALID_PASSWORD: + case NLogin::TLoginProvider::TLoginUserResponse::EStatus::INVALID_USER: + case NLogin::TLoginProvider::TLoginUserResponse::EStatus::UNAVAILABLE_KEY: + case NLogin::TLoginProvider::TLoginUserResponse::EStatus::UNSPECIFIED: { + result->Record.SetError(response.Error); + break; + } + } + Self->Send( + LoginFinalizeEventPtr->Get()->Source, + std::move(result), + 0, + LoginFinalizeEventPtr->Cookie + ); + } + + void SendError(const TString& error) { + auto result = MakeHolder(); + result->Record.SetError(error); + Self->Send( + LoginFinalizeEventPtr->Get()->Source, + std::move(result), + 0, + LoginFinalizeEventPtr->Cookie + ); + } + + void UpdateLoginSidsStats(const NLogin::TLoginProvider::TLoginUserResponse& response, TTransactionContext& txc) { + NIceDb::TNiceDb db(txc.DB); + switch (response.Status) { + case NLogin::TLoginProvider::TLoginUserResponse::EStatus::SUCCESS: { + const auto& sid = Self->LoginProvider.Sids[LoginFinalizeEventPtr->Get()->Request.User]; + db.Table() + .Key(LoginFinalizeEventPtr->Get()->Request.User) + .Update( + ToMicroSeconds(sid.LastSuccessfulLogin), sid.FailedLoginAttemptCount + ); + break; + } + case NLogin::TLoginProvider::TLoginUserResponse::EStatus::INVALID_PASSWORD: { + const auto& sid = Self->LoginProvider.Sids[LoginFinalizeEventPtr->Get()->Request.User]; + db.Table() + .Key(LoginFinalizeEventPtr->Get()->Request.User) + .Update( + ToMicroSeconds(sid.LastFailedLogin), sid.FailedLoginAttemptCount + ); + } + default: + break; + } + } +}; + +NTabletFlatExecutor::ITransaction* TSchemeShard::CreateTxLoginFinalize(TEvPrivate::TEvLoginFinalize::TPtr &ev) { + return new TTxLoginFinalize(this, ev); +} + +}} diff --git a/ydb/core/tx/schemeshard/schemeshard_impl.cpp b/ydb/core/tx/schemeshard/schemeshard_impl.cpp index 43494e643ba7..9560b3472850 100644 --- a/ydb/core/tx/schemeshard/schemeshard_impl.cpp +++ b/ydb/core/tx/schemeshard/schemeshard_impl.cpp @@ -1,5 +1,6 @@ #include "schemeshard.h" #include "schemeshard_impl.h" +#include "schemeshard_login_helper.h" #include "schemeshard_svp_migration.h" #include "olap/bg_tasks/adapter/adapter.h" #include "olap/bg_tasks/events/global.h" @@ -4682,6 +4683,7 @@ void TSchemeShard::Die(const TActorContext &ctx) { ctx.Send(SchemeBoardPopulator, new TEvents::TEvPoisonPill()); ctx.Send(TxAllocatorClient, new TEvents::TEvPoisonPill()); ctx.Send(SysPartitionStatsCollector, new TEvents::TEvPoisonPill()); + ctx.Send(LoginHelper, new TEvents::TEvPoisonPill()); if (TabletMigrator) { ctx.Send(TabletMigrator, new TEvents::TEvPoisonPill()); @@ -4798,6 +4800,8 @@ void TSchemeShard::OnActivateExecutor(const TActorContext &ctx) { Execute(CreateTxInitSchema(), ctx); SubscribeConsoleConfigs(ctx); + + LoginHelper = Register(CreateLoginHelper(this->LoginProvider).Release()); } // This is overriden as noop in order to activate the table only at the end of Init transaction @@ -5059,6 +5063,7 @@ void TSchemeShard::StateWork(STFUNC_SIG) { HFuncTraced(TEvPrivate::TEvPersistTopicStats, Handle); HFuncTraced(TEvSchemeShard::TEvLogin, Handle); + HFuncTraced(TEvPrivate::TEvLoginFinalize, Handle); HFuncTraced(TEvSchemeShard::TEvListUsers, Handle); HFuncTraced(TEvDataShard::TEvProposeTransactionAttachResult, Handle); @@ -7747,6 +7752,10 @@ void TSchemeShard::Handle(TEvSchemeShard::TEvLogin::TPtr &ev, const TActorContex Execute(CreateTxLogin(ev), ctx); } +void TSchemeShard::Handle(TEvPrivate::TEvLoginFinalize::TPtr &ev, const TActorContext &ctx) { + Execute(CreateTxLoginFinalize(ev), ctx); +} + void TSchemeShard::Handle(TEvSchemeShard::TEvListUsers::TPtr &ev, const TActorContext &ctx) { Execute(CreateTxListUsers(ev), ctx); } diff --git a/ydb/core/tx/schemeshard/schemeshard_impl.h b/ydb/core/tx/schemeshard/schemeshard_impl.h index 28d06f0d715e..5286e870ada6 100644 --- a/ydb/core/tx/schemeshard/schemeshard_impl.h +++ b/ydb/core/tx/schemeshard/schemeshard_impl.h @@ -1046,6 +1046,8 @@ class TSchemeShard struct TTxLogin; NTabletFlatExecutor::ITransaction* CreateTxLogin(TEvSchemeShard::TEvLogin::TPtr &ev); + struct TTxLoginFinalize; + NTabletFlatExecutor::ITransaction* CreateTxLoginFinalize(TEvPrivate::TEvLoginFinalize::TPtr &ev); struct TTxListUsers; NTabletFlatExecutor::ITransaction* CreateTxListUsers(TEvSchemeShard::TEvListUsers::TPtr &ev); @@ -1238,6 +1240,7 @@ class TSchemeShard void Handle(NConsole::TEvConsole::TEvConfigNotificationRequest::TPtr &ev, const TActorContext &ctx); void Handle(TEvSchemeShard::TEvLogin::TPtr& ev, const TActorContext& ctx); + void Handle(TEvPrivate::TEvLoginFinalize::TPtr& ev, const TActorContext& ctx); void Handle(TEvSchemeShard::TEvListUsers::TPtr& ev, const TActorContext& ctx); void RestartPipeTx(TTabletId tabletId, const TActorContext& ctx); @@ -1558,6 +1561,8 @@ class TSchemeShard void SetShardsQuota(ui64 value) override; NLogin::TLoginProvider LoginProvider; + TActorId LoginHelper; + THolder DataErasureManager = nullptr; private: diff --git a/ydb/core/tx/schemeshard/schemeshard_login_helper.cpp b/ydb/core/tx/schemeshard/schemeshard_login_helper.cpp new file mode 100644 index 000000000000..b6ea12572339 --- /dev/null +++ b/ydb/core/tx/schemeshard/schemeshard_login_helper.cpp @@ -0,0 +1,56 @@ +#include "schemeshard_login_helper.h" +#include "schemeshard_private.h" +#include +#include +#include +#include + +namespace NKikimr::NSchemeShard { + +class TLoginHelper : public NActors::TActorBootstrapped { +public: + explicit TLoginHelper(const NLogin::TLoginProvider& loginProvider) + : LoginProvider_(loginProvider) + {} + + void Bootstrap() { + Become(&TThis::StateWork); + } + + STATEFN(StateWork) { + switch (ev->GetTypeRewrite()) { + hFunc(TEvPrivate::TEvVerifyPassword, VerifyPassword); + cFunc(NActors::TEvents::TEvPoison::EventType, PassAway); + } + } + +private: + void VerifyPassword(TEvPrivate::TEvVerifyPassword::TPtr& ev) { + const bool isSuccessVerifying = LoginProvider_.VerifyHash(ev->Get()->Request, ev->Get()->PasswordHash); + if (!isSuccessVerifying) { + ev->Get()->CheckResult.Status = NLogin::TLoginProvider::TLoginUserResponse::EStatus::INVALID_PASSWORD; + ev->Get()->CheckResult.Error = "Invalid password"; + } + Send( + ev->Sender, + MakeHolder( + ev->Get()->Request, + ev->Get()->CheckResult, + ev->Get()->Source, + ev->Get()->PasswordHash, + /*needUpdateCache*/ true + ), + 0, + ev->Cookie + ); + } + +private: + const NLogin::TLoginProvider& LoginProvider_; +}; + +THolder CreateLoginHelper(const NLogin::TLoginProvider& loginProvider) { + return MakeHolder(loginProvider); +} + +} // namespace NKikimr::NSchemeShard diff --git a/ydb/core/tx/schemeshard/schemeshard_login_helper.h b/ydb/core/tx/schemeshard/schemeshard_login_helper.h new file mode 100644 index 000000000000..d17666bff732 --- /dev/null +++ b/ydb/core/tx/schemeshard/schemeshard_login_helper.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +namespace NLogin { + class TLoginProvider; +} + +namespace NKikimr::NSchemeShard { + +THolder CreateLoginHelper(const NLogin::TLoginProvider& loginProvider); + +} // namespace NKikimr::NSchemeShard diff --git a/ydb/core/tx/schemeshard/schemeshard_private.h b/ydb/core/tx/schemeshard/schemeshard_private.h index 85f51b3a2613..5ec9296be487 100644 --- a/ydb/core/tx/schemeshard/schemeshard_private.h +++ b/ydb/core/tx/schemeshard/schemeshard_private.h @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -43,6 +44,8 @@ namespace TEvPrivate { EvRunDataErasure, EvRunTenantDataErasure, EvAddNewShardToDataErasure, + EvVerifyPassword, + EvLoginFinalize, EvEnd }; @@ -273,6 +276,51 @@ namespace TEvPrivate { : Shards(std::move(shards)) {} }; + + struct TEvVerifyPassword : public NActors::TEventLocal { + public: + TEvVerifyPassword( + const NLogin::TLoginProvider::TLoginUserRequest& request, + const NLogin::TLoginProvider::TPasswordCheckResult& checkResult, + const NActors::TActorId source, + const TString& passwordHash + ) + : Request(request) + , CheckResult(checkResult) + , Source(source) + , PasswordHash(passwordHash) + {} + + public: + const NLogin::TLoginProvider::TLoginUserRequest Request; + NLogin::TLoginProvider::TPasswordCheckResult CheckResult; + const NActors::TActorId Source; // actorId of the initial schemeshard client which requested user login + const TString PasswordHash; + }; + + struct TEvLoginFinalize : public NActors::TEventLocal { + public: + TEvLoginFinalize( + const NLogin::TLoginProvider::TLoginUserRequest& request, + const NLogin::TLoginProvider::TPasswordCheckResult& checkResult, + const NActors::TActorId source, + const TString& passwordHash, + const bool needUpdateCache + ) + : Request(request) + , CheckResult(checkResult) + , Source(source) + , PasswordHash(passwordHash) + , NeedUpdateCache(needUpdateCache) + {} + + public: + const NLogin::TLoginProvider::TLoginUserRequest Request; + const NLogin::TLoginProvider::TPasswordCheckResult CheckResult; + const NActors::TActorId Source; // actorId of the initial schemeshard client which requested user login + const TString PasswordHash; + const bool NeedUpdateCache; + }; }; // TEvPrivate } // NSchemeShard diff --git a/ydb/core/tx/schemeshard/ut_helpers/helpers.cpp b/ydb/core/tx/schemeshard/ut_helpers/helpers.cpp index d378c5686502..d805a3e9f693 100644 --- a/ydb/core/tx/schemeshard/ut_helpers/helpers.cpp +++ b/ydb/core/tx/schemeshard/ut_helpers/helpers.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -2123,6 +2124,23 @@ namespace NSchemeShardUT_Private { return event->Record; } + NKikimrScheme::TEvLoginResult LoginFinalize( + TTestActorRuntime& runtime, + const NLogin::TLoginProvider::TLoginUserRequest& request, + const NLogin::TLoginProvider::TPasswordCheckResult& checkResult, + const TString& passwordHash, + const bool needUpdateCache + ) { + const auto evLoginFinalize = new NSchemeShard::TEvPrivate::TEvLoginFinalize( + request, checkResult, runtime.AllocateEdgeActor(), passwordHash, needUpdateCache + ); + AsyncSend(runtime, TTestTxConfig::SchemeShard, evLoginFinalize); + TAutoPtr handle; + const auto event = runtime.GrabEdgeEvent(handle); + UNIT_ASSERT(event); + return event->Record; + } + void ModifyUser(TTestActorRuntime& runtime, ui64 txId, const TString& database, std::function&& initiator) { auto modifyTx = std::make_unique(txId, TTestTxConfig::SchemeShard); auto transaction = modifyTx->Record.AddTransaction(); diff --git a/ydb/core/tx/schemeshard/ut_helpers/helpers.h b/ydb/core/tx/schemeshard/ut_helpers/helpers.h index 25863aece6c2..ccd06ae8623d 100644 --- a/ydb/core/tx/schemeshard/ut_helpers/helpers.h +++ b/ydb/core/tx/schemeshard/ut_helpers/helpers.h @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -556,6 +557,14 @@ namespace NSchemeShardUT_Private { NKikimrScheme::TEvLoginResult Login(TTestActorRuntime& runtime, const TString& user, const TString& password); + NKikimrScheme::TEvLoginResult LoginFinalize( + TTestActorRuntime& runtime, + const NLogin::TLoginProvider::TLoginUserRequest& request, + const NLogin::TLoginProvider::TPasswordCheckResult& checkResult, + const TString& passwordHash, + const bool needUpdateCache + ); + void ModifyUser(TTestActorRuntime& runtime, ui64 txId, const TString& database, std::function&& initiator); void ChangeIsEnabledUser(TTestActorRuntime& runtime, ui64 txId, const TString& database, diff --git a/ydb/core/tx/schemeshard/ut_login/ut_login.cpp b/ydb/core/tx/schemeshard/ut_login/ut_login.cpp index 348a572e2f6d..e271f1f481e8 100644 --- a/ydb/core/tx/schemeshard/ut_login/ut_login.cpp +++ b/ydb/core/tx/schemeshard/ut_login/ut_login.cpp @@ -1877,3 +1877,71 @@ Y_UNIT_TEST_SUITE(TWebLoginService) { } } } + +Y_UNIT_TEST_SUITE(TSchemeShardLoginFinalize) { + + void TestSuccess(const TVector& admins, const TString& testUser, bool isAdmin) { + TTestBasicRuntime runtime; + if (!admins.empty()) { + runtime.AddAppDataInit([&admins](ui32, NKikimr::TAppData& appData){ + for (const auto& admin : admins) { + appData.AdministrationAllowedSIDs.emplace_back(admin); + } + }); + } + TTestEnv env(runtime); + ui64 txId = 100; + + CreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", testUser, "password1"); + + const auto check = NLogin::TLoginProvider::TPasswordCheckResult{.Status = + NLogin::TLoginProvider::TPasswordCheckResult::EStatus::SUCCESS}; + const auto request = NLogin::TLoginProvider::TLoginUserRequest({.User = testUser}); + // public keys are filled after the first login + UNIT_ASSERT_VALUES_EQUAL(Login(runtime, testUser, "wrong-password1").error(), "Invalid password"); + const auto resultLogin = LoginFinalize(runtime, request, check, "", false); + UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), ""); + auto describe = DescribePath(runtime, TTestTxConfig::SchemeShard, "/MyRoot"); + CheckToken(resultLogin.token(), describe, testUser); + UNIT_ASSERT_VALUES_EQUAL(resultLogin.GetIsAdmin(), isAdmin); + } + + Y_UNIT_TEST(NoPublicKeys) { + TTestBasicRuntime runtime; + TTestEnv env(runtime); + ui64 txId = 100; + + CreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", "user1", "password1"); + + NLogin::TLoginProvider::TPasswordCheckResult check; + check.FillInvalidPassword(); + const auto request = NLogin::TLoginProvider::TLoginUserRequest({.User = "user1"}); + const auto resultLogin = LoginFinalize(runtime, request, check, "", false); + // public keys are filled after the first login + UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), "No key to generate token"); + UNIT_ASSERT_VALUES_EQUAL(resultLogin.token(), ""); + } + + Y_UNIT_TEST(InvalidPassword) { + TTestBasicRuntime runtime; + TTestEnv env(runtime); + ui64 txId = 100; + + CreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", "user1", "password1"); + + NLogin::TLoginProvider::TPasswordCheckResult check; + check.FillInvalidPassword(); + const auto request = NLogin::TLoginProvider::TLoginUserRequest({.User = "user1"}); + // public keys are filled after the first login + UNIT_ASSERT_VALUES_EQUAL(Login(runtime, "user1", "password1").error(), ""); + const auto resultLogin = LoginFinalize(runtime, request, check, "", false); + UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), "Invalid password"); + UNIT_ASSERT_VALUES_EQUAL(resultLogin.token(), ""); + } + + Y_UNIT_TEST(Success) { + TestSuccess({}, "user1", true); + TestSuccess({"user-admin"}, "user1", false); + TestSuccess({"user1"}, "user1", true); + } +} diff --git a/ydb/core/tx/schemeshard/ya.make b/ydb/core/tx/schemeshard/ya.make index 12ef7d6f8dfa..ac2264bdd247 100644 --- a/ydb/core/tx/schemeshard/ya.make +++ b/ydb/core/tx/schemeshard/ya.make @@ -85,6 +85,7 @@ SRCS( schemeshard__init_schema.cpp schemeshard__list_users.cpp schemeshard__login.cpp + schemeshard__login_finalize.cpp schemeshard__make_access_database_no_inheritable.cpp schemeshard__monitoring.cpp schemeshard__notify.cpp @@ -248,6 +249,8 @@ SRCS( schemeshard_import_scheme_query_executor.cpp schemeshard_info_types.cpp schemeshard_info_types.h + schemeshard_login_helper.cpp + schemeshard_login_helper.h schemeshard_path.cpp schemeshard_path.h schemeshard_path_describer.cpp diff --git a/ydb/library/login/login.cpp b/ydb/library/login/login.cpp index be33c1f193ce..c46e77cc1b7b 100644 --- a/ydb/library/login/login.cpp +++ b/ydb/library/login/login.cpp @@ -474,7 +474,7 @@ bool TLoginProvider::NeedVerifyHash(const TLoginUserRequest& request, TPasswordC Y_ENSURE(checkResult); Y_ENSURE(passwordHash); - if (FillNoKeys(checkResult)) { + if (FillUnavailableKey(checkResult)) { return false; } @@ -499,10 +499,9 @@ void TLoginProvider::UpdateCache(const TLoginUserRequest& request, const TString Impl->UpdateCache({.User = request.User, .Password = request.Password, .Hash = passwordHash}, isSuccessVerifying); } -bool TLoginProvider::FillNoKeys(TPasswordCheckResult* checkResult) const { +bool TLoginProvider::FillUnavailableKey(TPasswordCheckResult* checkResult) const { if (Keys.empty() || Keys.back().PrivateKey.empty()) { - checkResult->Status = TLoginUserResponse::EStatus::UNAVAILABLE_KEY; - checkResult->Error = "No key to generate token"; + checkResult->FillUnavailableKey(); return true; } return false; @@ -518,8 +517,7 @@ TLoginProvider::TSidRecord* TLoginProvider::GetUserSid(const TString& user) { bool TLoginProvider::FillInvalidUser(const TSidRecord* sid, TPasswordCheckResult* checkResult) const { if (!sid) { - checkResult->Status = TLoginUserResponse::EStatus::INVALID_USER; - checkResult->Error = "Invalid user"; + checkResult->FillInvalidUser("Invalid user"); return true; } return false; @@ -527,7 +525,16 @@ bool TLoginProvider::FillInvalidUser(const TSidRecord* sid, TPasswordCheckResult TLoginProvider::TLoginUserResponse TLoginProvider::LoginUser(const TLoginUserRequest& request, const TPasswordCheckResult& checkResult) { TLoginUserResponse response; - if (FillNoKeys(&response)) { + if (checkResult.Status == TLoginUserResponse::EStatus::UNAVAILABLE_KEY) { + response.FillUnavailableKey(); + return response; + } + if (checkResult.Status == TLoginUserResponse::EStatus::INVALID_USER) { + response.FillInvalidUser(checkResult.Error); + return response; + } + + if (FillUnavailableKey(&response)) { return response; } @@ -539,13 +546,13 @@ TLoginProvider::TLoginUserResponse TLoginProvider::LoginUser(const TLoginUserReq } if (checkResult.Status == TLoginUserResponse::EStatus::INVALID_PASSWORD) { - response.Status = checkResult.Status; - response.Error = checkResult.Error; + response.FillInvalidPassword(); sid->LastFailedLogin = std::chrono::system_clock::now(); sid->FailedLoginAttemptCount++; return response; } } + Y_ENSURE(!checkResult.Error); const TKeyRecord& key = Keys.back(); auto keyId = ToString(key.KeyId); @@ -599,8 +606,7 @@ TLoginProvider::TLoginUserResponse TLoginProvider::LoginUser(const TLoginUserReq const auto isSuccessVerifying = VerifyHash(request, passwordHash); UpdateCache(request, passwordHash, isSuccessVerifying); if (!isSuccessVerifying) { - checkResult.Status = TLoginUserResponse::EStatus::INVALID_PASSWORD; - checkResult.Error = "Invalid password"; + checkResult.FillInvalidPassword(); } } return LoginUser(request, checkResult); @@ -847,8 +853,7 @@ bool TLoginProvider::TImpl::NeedVerifyHash(const TLruCache::TKey& key, TPassword } if (WrongPasswordsCache.Find(key) != WrongPasswordsCache.End()) { - checkResult->Status = TLoginUserResponse::EStatus::INVALID_PASSWORD; - checkResult->Error = "Invalid password"; + checkResult->FillInvalidPassword(); return false; } diff --git a/ydb/library/login/login.h b/ydb/library/login/login.h index 049d784defec..335270d48e4d 100644 --- a/ydb/library/login/login.h +++ b/ydb/library/login/login.h @@ -69,6 +69,7 @@ class TLoginProvider { }; struct TPasswordCheckResult : TBasicResponse { + public: enum class EStatus { UNSPECIFIED, SUCCESS, @@ -78,6 +79,22 @@ class TLoginProvider { }; EStatus Status = EStatus::UNSPECIFIED; + + public: + void FillInvalidPassword() { + Status = TLoginUserResponse::EStatus::INVALID_PASSWORD; + Error = "Invalid password"; + } + + void FillUnavailableKey() { + Status = TLoginUserResponse::EStatus::UNAVAILABLE_KEY; + Error = "No key to generate token"; + } + + void FillInvalidUser(const TString& error) { + Status = TLoginUserResponse::EStatus::INVALID_USER; + Error = error; + } }; struct TLoginUserResponse : TPasswordCheckResult { @@ -255,7 +272,7 @@ class TLoginProvider { bool ShouldUnlockAccount(const TSidRecord& sid) const; bool CheckPasswordOrHash(bool IsHashedPassword, const TString& user, const TString& password, TString& error) const; TSidRecord* GetUserSid(const TString& user); - bool FillNoKeys(TPasswordCheckResult* checkResult) const; + bool FillUnavailableKey(TPasswordCheckResult* checkResult) const; bool FillInvalidUser(const TSidRecord* sid, TPasswordCheckResult* checkResult) const; private: diff --git a/ydb/library/login/login_ut.cpp b/ydb/library/login/login_ut.cpp index 31159b27f80f..46766aafd0bf 100644 --- a/ydb/library/login/login_ut.cpp +++ b/ydb/library/login/login_ut.cpp @@ -828,4 +828,20 @@ Y_UNIT_TEST_SUITE(Login) { UNIT_ASSERT(!loginResponse.Error); } } + + Y_UNIT_TEST(NotIgnoreCheckErrors) { + TLoginProvider provider(TPasswordComplexity(), TAccountLockout::TInitializer(), [] () {return true;}, {}); + provider.RotateKeys(); + + TLoginProvider::TPasswordCheckResult checkResult; + checkResult.FillUnavailableKey(); + auto response = provider.LoginUser(TLoginProvider::TLoginUserRequest{}, checkResult); + UNIT_ASSERT_EQUAL(response.Status, checkResult.Status); + UNIT_ASSERT_EQUAL(response.Error, checkResult.Error); + + checkResult.FillInvalidUser("bad user"); + response = provider.LoginUser(TLoginProvider::TLoginUserRequest{}, checkResult); + UNIT_ASSERT_EQUAL(response.Status, checkResult.Status); + UNIT_ASSERT_EQUAL(response.Error, checkResult.Error); + } }