aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndrey Molotkov <molotkov-and@ydb.tech>2024-12-20 14:16:49 +0300
committerGitHub <noreply@github.com>2024-12-20 11:16:49 +0000
commitbc46b2ce82722440d3e0b4226c9927b968ca1e7c (patch)
tree20971a2b115130c87451243285b2c6566b43ed89
parent4ca7f5cf75693c2f379b190f621e681917d88952 (diff)
downloadydb-bc46b2ce82722440d3e0b4226c9927b968ca1e7c.tar.gz
Automatically account lockout after some attempts to login with wrong password (#12578)oidc-1.0.0meta-1.0.0
-rw-r--r--ydb/core/protos/auth.proto6
-rw-r--r--ydb/core/tx/schemeshard/schemeshard__login.cpp139
-rw-r--r--ydb/core/tx/schemeshard/schemeshard_impl.cpp27
-rw-r--r--ydb/core/tx/schemeshard/schemeshard_impl.h14
-rw-r--r--ydb/core/tx/schemeshard/schemeshard_schema.h12
-rw-r--r--ydb/core/tx/schemeshard/ut_helpers/helpers.cpp22
-rw-r--r--ydb/core/tx/schemeshard/ut_helpers/helpers.h15
-rw-r--r--ydb/core/tx/schemeshard/ut_login/ut_login.cpp611
-rw-r--r--ydb/library/login/login.cpp4
-rw-r--r--ydb/library/login/login.h9
-rw-r--r--ydb/services/ydb/ydb_ldap_login_ut.cpp2
-rw-r--r--ydb/tests/functional/scheme_tests/canondata/tablet_scheme_tests.TestTabletSchemes.test_tablet_schemes_flat_schemeshard_/flat_schemeshard.schema20
-rw-r--r--ydb/tests/library/common/types.py2
13 files changed, 760 insertions, 123 deletions
diff --git a/ydb/core/protos/auth.proto b/ydb/core/protos/auth.proto
index e01eeda6bb..6e713d7f2c 100644
--- a/ydb/core/protos/auth.proto
+++ b/ydb/core/protos/auth.proto
@@ -57,6 +57,7 @@ message TAuthConfig {
optional bool EnableLoginAuthentication = 81 [default = true];
optional string NodeRegistrationToken = 82 [default = "root@builtin", (Ydb.sensitive) = true];
optional TPasswordComplexity PasswordComplexity = 83;
+ optional TAccountLockout AccountLockout = 84;
}
message TUserRegistryConfig {
@@ -133,3 +134,8 @@ message TPasswordComplexity {
optional string SpecialChars = 6;
optional bool CanContainUsername = 7;
}
+
+message TAccountLockout {
+ optional uint32 AttemptThreshold = 1 [default = 4];
+ optional string AttemptResetDuration = 2 [default = "1h"];
+}
diff --git a/ydb/core/tx/schemeshard/schemeshard__login.cpp b/ydb/core/tx/schemeshard/schemeshard__login.cpp
index 6a9408a3bd..faa80e91b9 100644
--- a/ydb/core/tx/schemeshard/schemeshard__login.cpp
+++ b/ydb/core/tx/schemeshard/schemeshard__login.cpp
@@ -8,13 +8,15 @@ namespace NSchemeShard {
using namespace NTabletFlatExecutor;
-struct TSchemeShard::TTxLogin : TSchemeShard::TRwTxBase {
+struct TSchemeShard::TTxLogin : TTransactionBase<TSchemeShard> {
TEvSchemeShard::TEvLogin::TPtr Request;
TPathId SubDomainPathId;
bool NeedPublishOnComplete = false;
+ THolder<TEvSchemeShard::TEvLoginResult> Result = MakeHolder<TEvSchemeShard::TEvLoginResult>();
+ size_t CurrentFailedAttemptCount = 0;
TTxLogin(TSelf *self, TEvSchemeShard::TEvLogin::TPtr &ev)
- : TRwTxBase(self)
+ : TTransactionBase<TSchemeShard>(self)
, Request(std::move(ev))
{}
@@ -34,10 +36,11 @@ struct TSchemeShard::TTxLogin : TSchemeShard::TRwTxBase {
};
}
- void DoExecute(TTransactionContext& txc, const TActorContext& ctx) override {
+ bool Execute(TTransactionContext& txc, const TActorContext& ctx) override {
LOG_DEBUG_S(ctx, NKikimrServices::FLAT_TX_SCHEMESHARD,
- "TTxLogin DoExecute"
+ "TTxLogin Execute"
<< " 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<ui64> keysExpired;
@@ -50,7 +53,6 @@ struct TSchemeShard::TTxLogin : TSchemeShard::TRwTxBase {
domainPtr->UpdateSecurityState(Self->LoginProvider.GetSecurityState());
domainPtr->IncSecurityStateVersion();
- NIceDb::TNiceDb db(txc.DB);
Self->PersistSubDomainSecurityStateVersion(db, SubDomainPathId, *domainPtr);
@@ -67,37 +69,130 @@ struct TSchemeShard::TTxLogin : TSchemeShard::TRwTxBase {
NeedPublishOnComplete = true;
}
+
+ return LoginAttempt(db, ctx);
}
- void DoComplete(const TActorContext &ctx) override {
+ void Complete(const TActorContext &ctx) override {
if (NeedPublishOnComplete) {
Self->PublishToSchemeBoard(TTxId(), {SubDomainPathId}, ctx);
}
- THolder<TEvSchemeShard::TEvLoginResult> result = MakeHolder<TEvSchemeShard::TEvLoginResult>();
+ LOG_DEBUG_S(ctx, NKikimrServices::FLAT_TX_SCHEMESHARD,
+ "TTxLogin Complete"
+ << ", result: " << Result->Record.ShortDebugString()
+ << ", at schemeshard: " << Self->TabletID());
+
+ ctx.Send(Request->Sender, std::move(Result), 0, Request->Cookie);
+ }
+
+private:
+ bool LoginAttempt(NIceDb::TNiceDb& db, const TActorContext& ctx) {
const auto& loginRequest = GetLoginRequest();
- if (loginRequest.ExternalAuth || AppData(ctx)->AuthConfig.GetEnableLoginAuthentication()) {
- NLogin::TLoginProvider::TLoginUserResponse loginResponse = Self->LoginProvider.LoginUser(loginRequest);
- if (loginResponse.Error) {
- result->Record.SetError(loginResponse.Error);
- }
- if (loginResponse.Token) {
- result->Record.SetToken(loginResponse.Token);
- result->Record.SetSanitizedToken(loginResponse.SanitizedToken);
+ if (!loginRequest.ExternalAuth && !AppData(ctx)->AuthConfig.GetEnableLoginAuthentication()) {
+ Result->Record.SetError("Login authentication is disabled");
+ return true;
+ }
+ if (loginRequest.ExternalAuth) {
+ return HandleExternalAuth(loginRequest);
+ }
+ return HandleLoginAuth(loginRequest, db, ctx);
+ }
+
+ bool 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);
+ 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;
+ }
+ }
+ return true;
+ }
+
+ bool HandleLoginAuth(const NLogin::TLoginProvider::TLoginUserRequest& loginRequest, NIceDb::TNiceDb& db, const TActorContext& ctx) {
+ auto row = db.Table<Schema::LoginSids>().Key(loginRequest.User).Select();
+ if (!row.IsReady()) {
+ return false;
+ }
+ if (!row.IsValid()) {
+ Result->Record.SetError(TStringBuilder() << "Cannot find user: " << loginRequest.User);
+ return true;
+ }
+ CurrentFailedAttemptCount = row.GetValueOrDefault<Schema::LoginSids::FailedAttemptCount>();
+ TInstant lastFailedAttempt = TInstant::FromValue(row.GetValue<Schema::LoginSids::LastFailedAttempt>());
+ if (CheckAccountLockout()) {
+ if (ShouldUnlockAccount(lastFailedAttempt)) {
+ UnlockAccount(loginRequest, db);
+ } else {
+ Result->Record.SetError(TStringBuilder() << "User " << loginRequest.User << " is locked out");
+ return true;
}
+ } else if (ShouldResetFailedAttemptCount(lastFailedAttempt)) {
+ ResetFailedAttemptCount(loginRequest, db);
+ }
+ const NLogin::TLoginProvider::TLoginUserResponse loginResponse = Self->LoginProvider.LoginUser(loginRequest);
+ switch (loginResponse.Status) {
+ case NLogin::TLoginProvider::TLoginUserResponse::EStatus::SUCCESS: {
+ HandleLoginAuthSuccess(loginRequest, loginResponse, db);
+ Result->Record.SetToken(loginResponse.Token);
+ Result->Record.SetSanitizedToken(loginResponse.SanitizedToken);
+ break;
+ }
+ case NLogin::TLoginProvider::TLoginUserResponse::EStatus::INVALID_PASSWORD: {
+ HandleLoginAuthInvalidPassword(loginRequest, loginResponse, db);
+ 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;
+ }
+ }
+ return true;
+ }
+
+ bool CheckAccountLockout() const {
+ return (Self->AccountLockout.AttemptThreshold != 0 && CurrentFailedAttemptCount >= Self->AccountLockout.AttemptThreshold);
+ }
- } else {
- result->Record.SetError("Login authentication is disabled");
+ bool ShouldResetFailedAttemptCount(const TInstant& lastFailedAttempt) {
+ if (Self->AccountLockout.AttemptResetDuration == TDuration::Zero()) {
+ return false;
}
+ return lastFailedAttempt + Self->AccountLockout.AttemptResetDuration < TAppData::TimeProvider->Now();
+ }
- LOG_DEBUG_S(ctx, NKikimrServices::FLAT_TX_SCHEMESHARD,
- "TTxLogin DoComplete"
- << ", result: " << result->Record.ShortDebugString()
- << ", at schemeshard: " << Self->TabletID());
+ bool ShouldUnlockAccount(const TInstant& lastFailedAttempt) {
+ return ShouldResetFailedAttemptCount(lastFailedAttempt);
+ }
+
+ void ResetFailedAttemptCount(const NLogin::TLoginProvider::TLoginUserRequest& loginRequest, NIceDb::TNiceDb& db) {
+ db.Table<Schema::LoginSids>().Key(loginRequest.User).Update<Schema::LoginSids::FailedAttemptCount>(Schema::LoginSids::FailedAttemptCount::Default);
+ CurrentFailedAttemptCount = Schema::LoginSids::FailedAttemptCount::Default;
+ }
+
+ void UnlockAccount(const NLogin::TLoginProvider::TLoginUserRequest& loginRequest, NIceDb::TNiceDb& db) {
+ ResetFailedAttemptCount(loginRequest, db);
+ }
- ctx.Send(Request->Sender, std::move(result), 0, Request->Cookie);
+ void HandleLoginAuthSuccess(const NLogin::TLoginProvider::TLoginUserRequest& loginRequest, const NLogin::TLoginProvider::TLoginUserResponse& loginResponse, NIceDb::TNiceDb& db) {
+ db.Table<Schema::LoginSids>().Key(loginRequest.User).Update<Schema::LoginSids::LastSuccessfulAttempt, Schema::LoginSids::FailedAttemptCount>(TAppData::TimeProvider->Now().MicroSeconds(), Schema::LoginSids::FailedAttemptCount::Default);
}
+ void HandleLoginAuthInvalidPassword(const NLogin::TLoginProvider::TLoginUserRequest& loginRequest, const NLogin::TLoginProvider::TLoginUserResponse& loginResponse, NIceDb::TNiceDb& db) {
+ db.Table<Schema::LoginSids>().Key(loginRequest.User).Update<Schema::LoginSids::LastFailedAttempt, Schema::LoginSids::FailedAttemptCount>(TAppData::TimeProvider->Now().MicroSeconds(), CurrentFailedAttemptCount + 1);
+ }
};
NTabletFlatExecutor::ITransaction* TSchemeShard::CreateTxLogin(TEvSchemeShard::TEvLogin::TPtr &ev) {
diff --git a/ydb/core/tx/schemeshard/schemeshard_impl.cpp b/ydb/core/tx/schemeshard/schemeshard_impl.cpp
index 9aca9b5724..da81487be9 100644
--- a/ydb/core/tx/schemeshard/schemeshard_impl.cpp
+++ b/ydb/core/tx/schemeshard/schemeshard_impl.cpp
@@ -4438,6 +4438,20 @@ TActorId TSchemeShard::TPipeClientFactory::CreateClient(const TActorContext& ctx
return clientId;
}
+TSchemeShard::TAccountLockout::TAccountLockout(const ::NKikimrProto::TAccountLockout& accountLockout)
+ : AttemptThreshold(accountLockout.GetAttemptThreshold())
+{
+ AttemptResetDuration = TDuration::Zero();
+ if (accountLockout.GetAttemptResetDuration().empty()) {
+ return;
+ }
+ if (TDuration::TryParse(accountLockout.GetAttemptResetDuration(), AttemptResetDuration)) {
+ if (AttemptResetDuration.Seconds() == 0) {
+ AttemptResetDuration = TDuration::Zero();
+ }
+ }
+}
+
TSchemeShard::TSchemeShard(const TActorId &tablet, TTabletStorageInfo *info)
: TActor(&TThis::StateInit)
, TTabletExecutedFlat(info, tablet, new NMiniKQL::TMiniKQLFactory)
@@ -4476,6 +4490,7 @@ TSchemeShard::TSchemeShard(const TActorId &tablet, TTabletStorageInfo *info)
.SpecialChars = AppData()->AuthConfig.GetPasswordComplexity().GetSpecialChars(),
.CanContainUsername = AppData()->AuthConfig.GetPasswordComplexity().GetCanContainUsername()
}))
+ , AccountLockout(AppData()->AuthConfig.GetAccountLockout())
{
TabletCountersPtr.Reset(new TProtobufTabletCounters<
ESimpleCounters_descriptor,
@@ -7162,6 +7177,7 @@ void TSchemeShard::ApplyConsoleConfigs(const NKikimrConfig::TAppConfig& appConfi
if (appConfig.HasAuthConfig()) {
ConfigureLoginProvider(appConfig.GetAuthConfig(), ctx);
+ ConfigureAccountLockout(appConfig.GetAuthConfig(), ctx);
}
if (IsSchemeShardConfigured()) {
@@ -7383,6 +7399,17 @@ void TSchemeShard::ConfigureLoginProvider(
<< ", CanContainUsername# " << (passwordComplexity.CanContainUsername ? "true" : "false"));
}
+void TSchemeShard::ConfigureAccountLockout(
+ const ::NKikimrProto::TAuthConfig& config,
+ const TActorContext &ctx)
+{
+ AccountLockout = TAccountLockout(config.GetAccountLockout());
+
+ LOG_NOTICE_S(ctx, NKikimrServices::FLAT_TX_SCHEMESHARD,
+ "AccountLockout configured: AttemptThreshold# " << AccountLockout.AttemptThreshold
+ << ", AttemptResetDuration# " << AccountLockout.AttemptResetDuration.ToString());
+}
+
void TSchemeShard::StartStopCompactionQueues() {
// note, that we don't need to check current state of compaction queue
if (IsServerlessDomain(TPath::Init(RootPathId(), this))) {
diff --git a/ydb/core/tx/schemeshard/schemeshard_impl.h b/ydb/core/tx/schemeshard/schemeshard_impl.h
index 7dc89abd05..2bcf17ab43 100644
--- a/ydb/core/tx/schemeshard/schemeshard_impl.h
+++ b/ydb/core/tx/schemeshard/schemeshard_impl.h
@@ -36,6 +36,7 @@
#include <ydb/core/protos/counters_schemeshard.pb.h>
#include <ydb/core/protos/filestore_config.pb.h>
#include <ydb/core/protos/flat_scheme_op.pb.h>
+#include <ydb/core/protos/auth.pb.h>
#include <ydb/core/sys_view/common/events.h>
#include <ydb/core/statistics/events.h>
#include <ydb/core/tablet/pipe_tracker.h>
@@ -500,6 +501,10 @@ public:
const ::NKikimrProto::TAuthConfig& config,
const TActorContext &ctx);
+ void ConfigureAccountLockout(
+ const ::NKikimrProto::TAuthConfig& config,
+ const TActorContext &ctx);
+
void StartStopCompactionQueues();
void WaitForTableProfiles(ui64 importId, ui32 itemIdx);
@@ -1463,6 +1468,15 @@ public:
NLogin::TLoginProvider LoginProvider;
+ struct TAccountLockout {
+ size_t AttemptThreshold = 4;
+ TDuration AttemptResetDuration = TDuration::Hours(1);
+
+ TAccountLockout(const ::NKikimrProto::TAccountLockout& accountLockout);
+ };
+
+ TAccountLockout AccountLockout;
+
private:
void OnDetach(const TActorContext &ctx) override;
void OnTabletDead(TEvTablet::TEvTabletDead::TPtr &ev, const TActorContext &ctx) override;
diff --git a/ydb/core/tx/schemeshard/schemeshard_schema.h b/ydb/core/tx/schemeshard/schemeshard_schema.h
index c3d154d6be..632616f6fc 100644
--- a/ydb/core/tx/schemeshard/schemeshard_schema.h
+++ b/ydb/core/tx/schemeshard/schemeshard_schema.h
@@ -1628,9 +1628,19 @@ struct Schema : NIceDb::Schema {
struct SidName : Column<1, NScheme::NTypeIds::String> {};
struct SidType : Column<2, NScheme::NTypeIds::Uint64> { using Type = NLoginProto::ESidType::SidType; };
struct SidHash : Column<3, NScheme::NTypeIds::String> {};
+ struct LastSuccessfulAttempt : Column<4, NScheme::NTypeIds::Timestamp> {};
+ struct LastFailedAttempt : Column<5, NScheme::NTypeIds::Timestamp> {};
+ struct FailedAttemptCount : Column<6, NScheme::NTypeIds::Uint32> {using Type = ui32; static constexpr Type Default = 0;};
using TKey = TableKey<SidName>;
- using TColumns = TableColumns<SidName, SidType, SidHash>;
+ using TColumns = TableColumns<
+ SidName,
+ SidType,
+ SidHash,
+ LastSuccessfulAttempt,
+ LastFailedAttempt,
+ FailedAttemptCount
+ >;
};
struct LoginSidMembers : Table<94> {
diff --git a/ydb/core/tx/schemeshard/ut_helpers/helpers.cpp b/ydb/core/tx/schemeshard/ut_helpers/helpers.cpp
index ded89e9701..3517ef7b07 100644
--- a/ydb/core/tx/schemeshard/ut_helpers/helpers.cpp
+++ b/ydb/core/tx/schemeshard/ut_helpers/helpers.cpp
@@ -17,6 +17,7 @@
#include <ydb/core/util/pb.h>
#include <ydb/public/api/protos/ydb_export.pb.h>
#include <ydb/core/protos/schemeshard/operations.pb.h>
+#include <ydb/core/protos/auth.pb.h>
#include <ydb/public/sdk/cpp/client/ydb_table/table.h>
#include <library/cpp/testing/unittest/registar.h>
@@ -1976,7 +1977,6 @@ namespace NSchemeShardUT_Private {
auto transaction = modifyTx->Record.AddTransaction();
transaction->SetWorkingDir(database);
transaction->SetOperationType(NKikimrSchemeOp::EOperationType::ESchemeOpAlterLogin);
-
auto removeUser = transaction->MutableAlterLogin()->MutableRemoveUser();
removeUser->SetUser(user);
@@ -1984,6 +1984,18 @@ namespace NSchemeShardUT_Private {
TestModificationResults(runtime, txId, expectedResults);
}
+ void CreateAlterLoginCreateGroup(TTestActorRuntime& runtime, ui64 txId, const TString& database, const TString& group, const TVector<TExpectedResult>& expectedResults) {
+ auto modifyTx = std::make_unique<TEvSchemeShard::TEvModifySchemeTransaction>(txId, TTestTxConfig::SchemeShard);
+ auto transaction = modifyTx->Record.AddTransaction();
+ transaction->SetWorkingDir(database);
+ transaction->SetOperationType(NKikimrSchemeOp::EOperationType::ESchemeOpAlterLogin);
+ auto createGroup = transaction->MutableAlterLogin()->MutableCreateGroup();
+ createGroup->SetGroup(group);
+
+ AsyncSend(runtime, TTestTxConfig::SchemeShard, modifyTx.release());
+ TestModificationResults(runtime, txId, expectedResults);
+ }
+
void AlterLoginAddGroupMembership(TTestActorRuntime& runtime, ui64 txId, const TString& database, const TString& member, const TString& group, const TVector<TExpectedResult>& expectedResults) {
auto modifyTx = std::make_unique<TEvSchemeShard::TEvModifySchemeTransaction>(txId, TTestTxConfig::SchemeShard);
auto transaction = modifyTx->Record.AddTransaction();
@@ -2010,13 +2022,17 @@ namespace NSchemeShardUT_Private {
AsyncSend(runtime, TTestTxConfig::SchemeShard, modifyTx.release());
TestModificationResults(runtime, txId, expectedResults);
- }
-
+ }
+
NKikimrScheme::TEvLoginResult Login(TTestActorRuntime& runtime, const TString& user, const TString& password) {
TActorId sender = runtime.AllocateEdgeActor();
auto evLogin = new TEvSchemeShard::TEvLogin();
evLogin->Record.SetUser(user);
evLogin->Record.SetPassword(password);
+
+ if (auto ldapDomain = runtime.GetAppData().AuthConfig.GetLdapAuthenticationDomain(); user.EndsWith("@" + ldapDomain)) {
+ evLogin->Record.SetExternalAuth(ldapDomain);
+ }
ForwardToTablet(runtime, TTestTxConfig::SchemeShard, sender, evLogin);
TAutoPtr<IEventHandle> handle;
auto event = runtime.GrabEdgeEvent<TEvSchemeShard::TEvLoginResult>(handle);
diff --git a/ydb/core/tx/schemeshard/ut_helpers/helpers.h b/ydb/core/tx/schemeshard/ut_helpers/helpers.h
index f5d4c79c54..04d2c0f194 100644
--- a/ydb/core/tx/schemeshard/ut_helpers/helpers.h
+++ b/ydb/core/tx/schemeshard/ut_helpers/helpers.h
@@ -523,7 +523,7 @@ namespace NSchemeShardUT_Private {
TTestActorRuntime& runtime, ui64 schemeShard, ui64 tabletId,
NKikimrScheme::TEvFindTabletSubDomainPathIdResult::EStatus expected = NKikimrScheme::TEvFindTabletSubDomainPathIdResult::SUCCESS);
- void CreateAlterLoginCreateUser(TTestActorRuntime& runtime, ui64 txId, const TString& database,
+ void CreateAlterLoginCreateUser(TTestActorRuntime& runtime, ui64 txId, const TString& database,
const TString& user, const TString& password,
const TVector<TExpectedResult>& expectedResults = {{NKikimrScheme::StatusSuccess}});
@@ -531,15 +531,18 @@ namespace NSchemeShardUT_Private {
const TString& user,
const TVector<TExpectedResult>& expectedResults = {{NKikimrScheme::StatusSuccess}});
- void AlterLoginAddGroupMembership(TTestActorRuntime& runtime, ui64 txId, const TString& database,
- const TString& member, const TString& group,
+ void CreateAlterLoginCreateGroup(TTestActorRuntime& runtime, ui64 txId, const TString& database,
+ const TString& group, const TVector<TExpectedResult>& expectedResults = {{NKikimrScheme::StatusSuccess}});
+
+ void AlterLoginAddGroupMembership(TTestActorRuntime& runtime, ui64 txId, const TString& database,
+ const TString& member, const TString& group,
const TVector<TExpectedResult>& expectedResults = {{NKikimrScheme::StatusSuccess}});
- void AlterLoginRemoveGroupMembership(TTestActorRuntime& runtime, ui64 txId, const TString& database,
- const TString& member, const TString& group,
+ void AlterLoginRemoveGroupMembership(TTestActorRuntime& runtime, ui64 txId, const TString& database,
+ const TString& member, const TString& group,
const TVector<TExpectedResult>& expectedResults = {{NKikimrScheme::StatusSuccess}});
- NKikimrScheme::TEvLoginResult Login(TTestActorRuntime& runtime,
+ NKikimrScheme::TEvLoginResult Login(TTestActorRuntime& runtime,
const TString& user, const TString& password);
// Mimics data query to a single table with multiple partitions
diff --git a/ydb/core/tx/schemeshard/ut_login/ut_login.cpp b/ydb/core/tx/schemeshard/ut_login/ut_login.cpp
index b713052e5f..f35f400520 100644
--- a/ydb/core/tx/schemeshard/ut_login/ut_login.cpp
+++ b/ydb/core/tx/schemeshard/ut_login/ut_login.cpp
@@ -33,6 +33,42 @@ void SetPasswordCheckerParameters(TTestActorRuntime &runtime, ui64 schemeShard,
SetConfig(runtime, schemeShard, std::move(request));
}
+struct TAccountLockoutInitializer {
+ size_t AttemptThreshold = 4;
+ TString AttemptResetDuration = "1h";
+};
+
+void SetAccountLockoutParameters(TTestActorRuntime &runtime, ui64 schemeShard, const TAccountLockoutInitializer& initializer) {
+ auto request = MakeHolder<NConsole::TEvConsole::TEvConfigNotificationRequest>();
+
+ ::NKikimrProto::TAccountLockout accountLockout;
+ accountLockout.SetAttemptThreshold(initializer.AttemptThreshold);
+ accountLockout.SetAttemptResetDuration(initializer.AttemptResetDuration);
+ *request->Record.MutableConfig()->MutableAuthConfig()->MutableAccountLockout() = accountLockout;
+ SetConfig(runtime, schemeShard, std::move(request));
+}
+
+struct TCheckSecurityStateExpectedValues {
+ size_t PublicKeysSize;
+ size_t SidsSize;
+};
+
+void CheckSecurityState(const NKikimrScheme::TEvDescribeSchemeResult& describeResult, const TCheckSecurityStateExpectedValues& expectedValues) {
+ UNIT_ASSERT(describeResult.HasPathDescription());
+ UNIT_ASSERT(describeResult.GetPathDescription().HasDomainDescription());
+ UNIT_ASSERT(describeResult.GetPathDescription().GetDomainDescription().HasSecurityState());
+ UNIT_ASSERT_VALUES_EQUAL(describeResult.GetPathDescription().GetDomainDescription().GetSecurityState().PublicKeysSize(), expectedValues.PublicKeysSize);
+ UNIT_ASSERT_VALUES_EQUAL(describeResult.GetPathDescription().GetDomainDescription().GetSecurityState().SidsSize(), expectedValues.SidsSize);
+}
+
+void CheckToken(const TString& token, const NKikimrScheme::TEvDescribeSchemeResult& describeResult, const TString& expectedUsername, const TString& expectedError = "") {
+ NLogin::TLoginProvider login;
+ login.UpdateSecurityState(describeResult.GetPathDescription().GetDomainDescription().GetSecurityState());
+ auto validateResult = login.ValidateToken({.Token = token});
+ UNIT_ASSERT_VALUES_EQUAL(validateResult.Error, expectedError);
+ UNIT_ASSERT_VALUES_EQUAL(validateResult.User, expectedUsername);
+}
+
} // namespace NSchemeShardUT_Private
Y_UNIT_TEST_SUITE(TSchemeShardLoginTest) {
@@ -45,22 +81,14 @@ Y_UNIT_TEST_SUITE(TSchemeShardLoginTest) {
{
auto describe = DescribePath(runtime, TTestTxConfig::SchemeShard, "/MyRoot");
Cerr << describe.DebugString() << Endl;
- UNIT_ASSERT(describe.HasPathDescription());
- UNIT_ASSERT(describe.GetPathDescription().HasDomainDescription());
- UNIT_ASSERT(describe.GetPathDescription().GetDomainDescription().HasSecurityState());
- UNIT_ASSERT_VALUES_EQUAL(describe.GetPathDescription().GetDomainDescription().GetSecurityState().PublicKeysSize(), 0);
- UNIT_ASSERT_VALUES_EQUAL(describe.GetPathDescription().GetDomainDescription().GetSecurityState().SidsSize(), 0);
+ CheckSecurityState(describe, {.PublicKeysSize = 0, .SidsSize = 0});
}
CreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", "user1", "password1");
-
+
{
auto describe = DescribePath(runtime, TTestTxConfig::SchemeShard, "/MyRoot");
- UNIT_ASSERT(describe.HasPathDescription());
- UNIT_ASSERT(describe.GetPathDescription().HasDomainDescription());
- UNIT_ASSERT(describe.GetPathDescription().GetDomainDescription().HasSecurityState());
- UNIT_ASSERT_VALUES_EQUAL(describe.GetPathDescription().GetDomainDescription().GetSecurityState().PublicKeysSize(), 0);
- UNIT_ASSERT_VALUES_EQUAL(describe.GetPathDescription().GetDomainDescription().GetSecurityState().SidsSize(), 1);
+ CheckSecurityState(describe, {.PublicKeysSize = 0, .SidsSize = 1});
}
// public keys are filled after the first login
@@ -69,20 +97,13 @@ Y_UNIT_TEST_SUITE(TSchemeShardLoginTest) {
{
auto describe = DescribePath(runtime, TTestTxConfig::SchemeShard, "/MyRoot");
- UNIT_ASSERT(describe.HasPathDescription());
- UNIT_ASSERT(describe.GetPathDescription().HasDomainDescription());
- UNIT_ASSERT(describe.GetPathDescription().GetDomainDescription().HasSecurityState());
- UNIT_ASSERT_VALUES_EQUAL(describe.GetPathDescription().GetDomainDescription().GetSecurityState().PublicKeysSize(), 1);
- UNIT_ASSERT_VALUES_EQUAL(describe.GetPathDescription().GetDomainDescription().GetSecurityState().SidsSize(), 1);
+ CheckSecurityState(describe, {.PublicKeysSize = 1, .SidsSize = 1});
}
// check token
{
auto describe = DescribePath(runtime, TTestTxConfig::SchemeShard, "/MyRoot");
- NLogin::TLoginProvider login;
- login.UpdateSecurityState(describe.GetPathDescription().GetDomainDescription().GetSecurityState());
- auto resultValidate = login.ValidateToken({.Token = resultLogin.token()});
- UNIT_ASSERT_VALUES_EQUAL(resultValidate.User, "user1");
+ CheckToken(resultLogin.token(), describe, "user1");
}
}
@@ -93,7 +114,7 @@ Y_UNIT_TEST_SUITE(TSchemeShardLoginTest) {
CreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", "user1", "password1");
auto resultLogin = Login(runtime, "user1", "password1");
UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), "");
-
+
AsyncMkDir(runtime, ++txId, "/MyRoot", "Dir1");
TestModificationResult(runtime, txId, NKikimrScheme::StatusAccepted);
AsyncMkDir(runtime, ++txId, "/MyRoot", "Dir2");
@@ -137,7 +158,7 @@ Y_UNIT_TEST_SUITE(TSchemeShardLoginTest) {
// check login
{
auto resultLogin = Login(runtime, "user1", "password1");
- UNIT_ASSERT_VALUES_EQUAL(resultLogin.GetError(), "Invalid user");
+ UNIT_ASSERT_VALUES_EQUAL(resultLogin.GetError(), "Cannot find user: user1");
}
// another still has access
@@ -152,7 +173,7 @@ Y_UNIT_TEST_SUITE(TSchemeShardLoginTest) {
CreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", "user1", "password1");
auto resultLogin = Login(runtime, "user1", "password1");
UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), "");
-
+
AsyncMkDir(runtime, ++txId, "/MyRoot", "Dir1");
TestModificationResult(runtime, txId, NKikimrScheme::StatusAccepted);
AsyncMkDir(runtime, ++txId, "/MyRoot", "Dir2");
@@ -192,7 +213,7 @@ Y_UNIT_TEST_SUITE(TSchemeShardLoginTest) {
// check login
{
auto resultLogin = Login(runtime, "user1", "password1");
- UNIT_ASSERT_VALUES_EQUAL(resultLogin.GetError(), "Invalid user");
+ UNIT_ASSERT_VALUES_EQUAL(resultLogin.GetError(), "Cannot find user: user1");
}
// another still has access
@@ -204,7 +225,7 @@ Y_UNIT_TEST_SUITE(TSchemeShardLoginTest) {
TTestBasicRuntime runtime;
TTestEnv env(runtime);
ui64 txId = 100;
-
+
for (bool missingOk : { false, true}) {
auto modifyTx = std::make_unique<TEvSchemeShard::TEvModifySchemeTransaction>(txId, TTestTxConfig::SchemeShard);
auto transaction = modifyTx->Record.AddTransaction();
@@ -230,7 +251,7 @@ Y_UNIT_TEST_SUITE(TSchemeShardLoginTest) {
CreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", "user1", "password1");
auto resultLogin = Login(runtime, "user1", "password1");
UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), "");
-
+
AsyncMkDir(runtime, ++txId, "/MyRoot", "Dir1/DirSub1");
NACLib::TDiffACL diffACL;
@@ -246,7 +267,7 @@ Y_UNIT_TEST_SUITE(TSchemeShardLoginTest) {
CreateAlterLoginRemoveUser(runtime, ++txId, "/MyRoot", "user1",
TVector<TExpectedResult>{{NKikimrScheme::StatusPreconditionFailed, "User user1 owns /MyRoot/Dir1/DirSub1 and can't be removed"}});
-
+
// check user still exists and has their rights:
{
TestDescribeResult(DescribePath(runtime, "/MyRoot/Dir1/DirSub1"),
@@ -264,130 +285,542 @@ Y_UNIT_TEST_SUITE(TSchemeShardLoginTest) {
TestDescribeResult(DescribePath(runtime, "/MyRoot/Dir1/DirSub1"),
{NLs::HasNoRight("+U:user1"), NLs::HasNoEffectiveRight("+U:user1"), NLs::HasOwner("user2")});
auto resultLogin = Login(runtime, "user1", "password1");
- UNIT_ASSERT_VALUES_EQUAL(resultLogin.GetError(), "Invalid user");
+ UNIT_ASSERT_VALUES_EQUAL(resultLogin.GetError(), "Cannot find user: user1");
}
}
+ Y_UNIT_TEST(TestExternalLogin) {
+ TTestBasicRuntime runtime;
+ TTestEnv env(runtime);
+ auto resultLogin = Login(runtime, "user1@ldap", "password1");
+ UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), "");
+ auto describe = DescribePath(runtime, TTestTxConfig::SchemeShard, "/MyRoot");
+ CheckSecurityState(describe, {.PublicKeysSize = 1, .SidsSize = 0});
+ CheckToken(resultLogin.token(), describe, "user1@ldap");
+ }
+
+ Y_UNIT_TEST(TestExternalLoginWithIncorrectLdapDomain) {
+ TTestBasicRuntime runtime;
+ TTestEnv env(runtime);
+ auto resultLogin = Login(runtime, "ldapuser@ldap.domain", "password1");
+ UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), "Cannot find user: ldapuser@ldap.domain");
+ UNIT_ASSERT_VALUES_EQUAL(resultLogin.token(), "");
+ auto describe = DescribePath(runtime, TTestTxConfig::SchemeShard, "/MyRoot");
+ CheckSecurityState(describe, {.PublicKeysSize = 1, .SidsSize = 0});
+ }
+
Y_UNIT_TEST(DisableBuiltinAuthMechanism) {
TTestBasicRuntime runtime;
TTestEnv env(runtime);
runtime.GetAppData().AuthConfig.SetEnableLoginAuthentication(false);
ui64 txId = 100;
- CreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", "user1", "password1", {{NKikimrScheme::StatusPreconditionFailed, "Login authentication is disabled"}});
+
+ {
+ auto describe = DescribePath(runtime, TTestTxConfig::SchemeShard, "/MyRoot");
+ Cerr << describe.DebugString() << Endl;
+ CheckSecurityState(describe, {.PublicKeysSize = 0, .SidsSize = 0});
+ }
+
+ CreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", "user1", "password1", {{NKikimrScheme::StatusPreconditionFailed}});
auto resultLogin = Login(runtime, "user1", "password1");
UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), "Login authentication is disabled");
UNIT_ASSERT_VALUES_EQUAL(resultLogin.token(), "");
- auto describe = DescribePath(runtime, TTestTxConfig::SchemeShard, "/MyRoot");
- UNIT_ASSERT(describe.HasPathDescription());
- UNIT_ASSERT(describe.GetPathDescription().HasDomainDescription());
- UNIT_ASSERT(describe.GetPathDescription().GetDomainDescription().HasSecurityState());
- UNIT_ASSERT(describe.GetPathDescription().GetDomainDescription().GetSecurityState().PublicKeysSize() > 0);
+
+ {
+ auto describe = DescribePath(runtime, TTestTxConfig::SchemeShard, "/MyRoot");
+ CheckSecurityState(describe, {.PublicKeysSize = 1, .SidsSize = 0});
+ }
+ }
+
+ Y_UNIT_TEST(FailedLoginWithInvalidUser) {
+ TTestBasicRuntime runtime;
+ TTestEnv env(runtime);
+ ui64 txId = 100;
+
+ {
+ auto describe = DescribePath(runtime, TTestTxConfig::SchemeShard, "/MyRoot");
+ Cerr << describe.DebugString() << Endl;
+ CheckSecurityState(describe, {.PublicKeysSize = 0, .SidsSize = 0});
+ }
+
+ CreateAlterLoginCreateGroup(runtime, ++txId, "/MyRoot", "group1");
+ auto resultLogin = Login(runtime, "group1", "password1");
+ UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), "Invalid user");
+ UNIT_ASSERT_VALUES_EQUAL(resultLogin.token(), "");
+
+ {
+ auto describe = DescribePath(runtime, TTestTxConfig::SchemeShard, "/MyRoot");
+ CheckSecurityState(describe, {.PublicKeysSize = 1, .SidsSize = 1});
+ }
}
Y_UNIT_TEST(ChangeAcceptablePasswordParameters) {
TTestBasicRuntime runtime;
TTestEnv env(runtime);
ui64 txId = 100;
+
+ {
+ auto describe = DescribePath(runtime, TTestTxConfig::SchemeShard, "/MyRoot");
+ Cerr << describe.DebugString() << Endl;
+ CheckSecurityState(describe, {.PublicKeysSize = 0, .SidsSize = 0});
+ }
+
// Password parameters:
// min length 0
// optional: lower case, upper case, numbers, special symbols from list !@#$%^&*()_+{}|<>?=
// required: cannot contain username
- CreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", "user1", "password1");
- auto resultLogin = Login(runtime, "user1", "password1");
- UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), "");
- auto describe = DescribePath(runtime, TTestTxConfig::SchemeShard, "/MyRoot");
- UNIT_ASSERT(describe.HasPathDescription());
- UNIT_ASSERT(describe.GetPathDescription().HasDomainDescription());
- UNIT_ASSERT(describe.GetPathDescription().GetDomainDescription().HasSecurityState());
- UNIT_ASSERT(describe.GetPathDescription().GetDomainDescription().GetSecurityState().PublicKeysSize() > 0);
+
+ {
+ CreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", "user1", "password1");
+ auto resultLogin = Login(runtime, "user1", "password1");
+ UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), "");
+ auto describe = DescribePath(runtime, TTestTxConfig::SchemeShard, "/MyRoot");
+ CheckSecurityState(describe, {.PublicKeysSize = 1, .SidsSize = 1});
+ CheckToken(resultLogin.token(), describe, "user1");
+ }
// Accept password without lower case symbols
- CreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", "user2", "PASSWORDU2");
- resultLogin = Login(runtime, "user2", "PASSWORDU2");
- UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), "");
+ {
+ CreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", "user2", "PASSWORDU2");
+ auto resultLogin = Login(runtime, "user2", "PASSWORDU2");
+ UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), "");
+ auto describe = DescribePath(runtime, TTestTxConfig::SchemeShard, "/MyRoot");
+ CheckSecurityState(describe, {.PublicKeysSize = 1, .SidsSize = 2});
+ CheckToken(resultLogin.token(), describe, "user2");
+ }
+
// Password parameters:
// min length 0
// optional: upper case, numbers, special symbols from list !@#$%^&*()_+{}|<>?=
// required: lower case = 3, cannot contain username
- SetPasswordCheckerParameters(runtime, TTestTxConfig::SchemeShard, {.MinLowerCaseCount = 3});
- CreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", "user3", "PASSWORDU3", {{NKikimrScheme::StatusPreconditionFailed, "Incorrect password format: should contain at least 3 lower case character"}});
+ {
+ SetPasswordCheckerParameters(runtime, TTestTxConfig::SchemeShard, {.MinLowerCaseCount = 3});
+ CreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", "user3", "PASSWORDU3", {{NKikimrScheme::StatusPreconditionFailed, "Incorrect password format: should contain at least 3 lower case character"}});
+ auto resultLogin = Login(runtime, "user3", "PASSWORDU3");
+ UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), "Cannot find user: user3");
+ auto describe = DescribePath(runtime, TTestTxConfig::SchemeShard, "/MyRoot");
+ CheckSecurityState(describe, {.PublicKeysSize = 1, .SidsSize = 2});
+ }
+
// Add lower case symbols to password
- CreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", "user3", "PASswORDu3");
- resultLogin = Login(runtime, "user3", "PASswORDu3");
- UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), "");
+ {
+ CreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", "user3", "PASswORDu3");
+ auto resultLogin = Login(runtime, "user3", "PASswORDu3");
+ UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), "");
+ auto describe = DescribePath(runtime, TTestTxConfig::SchemeShard, "/MyRoot");
+ CheckSecurityState(describe, {.PublicKeysSize = 1, .SidsSize = 3});
+ CheckToken(resultLogin.token(), describe, "user3");
+ }
// Accept password without upper case symbols
- CreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", "user4", "passwordu4");
- resultLogin = Login(runtime, "user4", "passwordu4");
- UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), "");
+ {
+ CreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", "user4", "passwordu4");
+ auto resultLogin = Login(runtime, "user4", "passwordu4");
+ UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), "");
+ auto describe = DescribePath(runtime, TTestTxConfig::SchemeShard, "/MyRoot");
+ CheckSecurityState(describe, {.PublicKeysSize = 1, .SidsSize = 4});
+ CheckToken(resultLogin.token(), describe, "user4");
+ }
+
// Password parameters:
// min length 0
// optional: lower case, numbers, special symbols from list !@#$%^&*()_+{}|<>?=
// required: upper case = 3, cannot contain username
- SetPasswordCheckerParameters(runtime, TTestTxConfig::SchemeShard, {.MinLowerCaseCount = 0, .MinUpperCaseCount = 3});
- CreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", "user5", "passwordu5", {{NKikimrScheme::StatusPreconditionFailed, "Incorrect password format: should contain at least 3 upper case character"}});
+ {
+ SetPasswordCheckerParameters(runtime, TTestTxConfig::SchemeShard, {.MinLowerCaseCount = 0, .MinUpperCaseCount = 3});
+ CreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", "user5", "passwordu5", {{NKikimrScheme::StatusPreconditionFailed, "Incorrect password format: should contain at least 3 upper case character"}});
+ auto resultLogin = Login(runtime, "user5", "passwordu5");
+ UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), "Cannot find user: user5");
+ auto describe = DescribePath(runtime, TTestTxConfig::SchemeShard, "/MyRoot");
+ CheckSecurityState(describe, {.PublicKeysSize = 1, .SidsSize = 4});
+ }
+
// Add 3 upper case symbols to password
- CreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", "user5", "PASswORDu5");
- resultLogin = Login(runtime, "user5", "PASswORDu5");
- UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), "");
+ {
+ CreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", "user5", "PASswORDu5");
+ auto resultLogin = Login(runtime, "user5", "PASswORDu5");
+ UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), "");
+ auto describe = DescribePath(runtime, TTestTxConfig::SchemeShard, "/MyRoot");
+ CheckSecurityState(describe, {.PublicKeysSize = 1, .SidsSize = 5});
+ CheckToken(resultLogin.token(), describe, "user5");
+ }
// Accept short password
- SetPasswordCheckerParameters(runtime, TTestTxConfig::SchemeShard, {.MinUpperCaseCount = 0});
- CreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", "user6", "passwu6");
- resultLogin = Login(runtime, "user6", "passwu6");
- UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), "");
+ {
+ SetPasswordCheckerParameters(runtime, TTestTxConfig::SchemeShard, {.MinUpperCaseCount = 0});
+ CreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", "user6", "passwu6");
+ auto resultLogin = Login(runtime, "user6", "passwu6");
+ UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), "");
+ auto describe = DescribePath(runtime, TTestTxConfig::SchemeShard, "/MyRoot");
+ CheckSecurityState(describe, {.PublicKeysSize = 1, .SidsSize = 6});
+ CheckToken(resultLogin.token(), describe, "user6");
+ }
+
// Password parameters:
// min length 8
// optional: lower case, upper case, numbers, special symbols from list !@#$%^&*()_+{}|<>?=
// required: cannot contain username
- SetPasswordCheckerParameters(runtime, TTestTxConfig::SchemeShard, {.MinLength = 8});
// Too short password
- CreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", "user7", "passwu7", {{NKikimrScheme::StatusPreconditionFailed, "Password is too short"}});
+ {
+ SetPasswordCheckerParameters(runtime, TTestTxConfig::SchemeShard, {.MinLength = 8});
+ CreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", "user7", "passwu7", {{NKikimrScheme::StatusPreconditionFailed, "Password is too short"}});
+ auto resultLogin = Login(runtime, "user7", "passwu7");
+ UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), "Cannot find user: user7");
+ auto describe = DescribePath(runtime, TTestTxConfig::SchemeShard, "/MyRoot");
+ CheckSecurityState(describe, {.PublicKeysSize = 1, .SidsSize = 6});
+ }
+
// Password has correct length
- CreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", "user7", "passwordu7");
- resultLogin = Login(runtime, "user7", "passwordu7");
- UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), "");
+ {
+ CreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", "user7", "passwordu7");
+ auto resultLogin = Login(runtime, "user7", "passwordu7");
+ UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), "");
+ auto describe = DescribePath(runtime, TTestTxConfig::SchemeShard, "/MyRoot");
+ CheckSecurityState(describe, {.PublicKeysSize = 1, .SidsSize = 7});
+ CheckToken(resultLogin.token(), describe, "user7");
+ }
// Accept password without numbers
- SetPasswordCheckerParameters(runtime, TTestTxConfig::SchemeShard, {.MinLength = 0});
- CreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", "user8", "passWorDueitgh");
- resultLogin = Login(runtime, "user8", "passWorDueitgh");
- UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), "");
+ {
+ SetPasswordCheckerParameters(runtime, TTestTxConfig::SchemeShard, {.MinLength = 0});
+ CreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", "user8", "passWorDueitgh");
+ auto resultLogin = Login(runtime, "user8", "passWorDueitgh");
+ UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), "");
+ auto describe = DescribePath(runtime, TTestTxConfig::SchemeShard, "/MyRoot");
+ CheckSecurityState(describe, {.PublicKeysSize = 1, .SidsSize = 8});
+ CheckToken(resultLogin.token(), describe, "user8");
+ }
+
// Password parameters:
// min length 0
// optional: lower case, upper case,special symbols from list !@#$%^&*()_+{}|<>?=
// required: numbers = 3, cannot contain username
- SetPasswordCheckerParameters(runtime, TTestTxConfig::SchemeShard, {.MinNumbersCount = 3});
- CreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", "user9", "passwordunine", {{NKikimrScheme::StatusPreconditionFailed, "Incorrect password format: should contain at least 3 number"}});
+ {
+ SetPasswordCheckerParameters(runtime, TTestTxConfig::SchemeShard, {.MinNumbersCount = 3});
+ CreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", "user9", "passwordunine", {{NKikimrScheme::StatusPreconditionFailed, "Incorrect password format: should contain at least 3 number"}});
+ auto resultLogin = Login(runtime, "user9", "passwordunine");
+ UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), "Cannot find user: user9");
+ auto describe = DescribePath(runtime, TTestTxConfig::SchemeShard, "/MyRoot");
+ CheckSecurityState(describe, {.PublicKeysSize = 1, .SidsSize = 8});
+ }
+
// Password with numbers
- CreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", "user9", "pas1swo5rdu9");
- resultLogin = Login(runtime, "user9", "pas1swo5rdu9");
- UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), "");
+ {
+ CreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", "user9", "pas1swo5rdu9");
+ auto resultLogin = Login(runtime, "user9", "pas1swo5rdu9");
+ UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), "");
+ auto describe = DescribePath(runtime, TTestTxConfig::SchemeShard, "/MyRoot");
+ CheckSecurityState(describe, {.PublicKeysSize = 1, .SidsSize = 9});
+ CheckToken(resultLogin.token(), describe, "user9");
+ }
// Accept password without special symbols
- SetPasswordCheckerParameters(runtime, TTestTxConfig::SchemeShard, {.MinNumbersCount = 0});
- CreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", "user10", "passWorDu10");
- resultLogin = Login(runtime, "user10", "passWorDu10");
- UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), "");
+ {
+ SetPasswordCheckerParameters(runtime, TTestTxConfig::SchemeShard, {.MinNumbersCount = 0});
+ CreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", "user10", "passWorDu10");
+ auto resultLogin = Login(runtime, "user10", "passWorDu10");
+ UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), "");
+ auto describe = DescribePath(runtime, TTestTxConfig::SchemeShard, "/MyRoot");
+ CheckSecurityState(describe, {.PublicKeysSize = 1, .SidsSize = 10});
+ CheckToken(resultLogin.token(), describe, "user10");
+ }
+
// Password parameters:
// min length 0
// optional: lower case, upper case, numbers
// required: special symbols from list !@#$%^&*()_+{}|<>?= , cannot contain username
- SetPasswordCheckerParameters(runtime, TTestTxConfig::SchemeShard, {.MinSpecialCharsCount = 3});
- CreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", "user11", "passwordu11", {{NKikimrScheme::StatusPreconditionFailed, "Incorrect password format: should contain at least 3 special character"}});
+ {
+ SetPasswordCheckerParameters(runtime, TTestTxConfig::SchemeShard, {.MinSpecialCharsCount = 3});
+ CreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", "user11", "passwordu11", {{NKikimrScheme::StatusPreconditionFailed, "Incorrect password format: should contain at least 3 special character"}});
+ auto resultLogin = Login(runtime, "user11", "passwordu11");
+ UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), "Cannot find user: user11");
+ auto describe = DescribePath(runtime, TTestTxConfig::SchemeShard, "/MyRoot");
+ CheckSecurityState(describe, {.PublicKeysSize = 1, .SidsSize = 10});
+ }
+
// Password with special symbols
- CreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", "user11", "passwordu11*&%#");
- resultLogin = Login(runtime, "user11", "passwordu11*&%#");
- UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), "");
+ {
+ CreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", "user11", "passwordu11*&%#");
+ auto resultLogin = Login(runtime, "user11", "passwordu11*&%#");
+ UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), "");
+ auto describe = DescribePath(runtime, TTestTxConfig::SchemeShard, "/MyRoot");
+ CheckSecurityState(describe, {.PublicKeysSize = 1, .SidsSize = 11});
+ CheckToken(resultLogin.token(), describe, "user11");
+ }
+
// Password parameters:
// min length 0
// optional: lower case, upper case, numbers
// required: special symbols from list *# , cannot contain username
- SetPasswordCheckerParameters(runtime, TTestTxConfig::SchemeShard, {.SpecialChars = "*#"}); // Only 2 special symbols are valid
- CreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", "user12", "passwordu12*&%#", {{NKikimrScheme::StatusPreconditionFailed, "Password contains unacceptable characters"}});
- CreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", "user12", "passwordu12*#");
- resultLogin = Login(runtime, "user12", "passwordu12*#");
+ {
+ SetPasswordCheckerParameters(runtime, TTestTxConfig::SchemeShard, {.SpecialChars = "*#"}); // Only 2 special symbols are valid
+ CreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", "user12", "passwordu12*&%#", {{NKikimrScheme::StatusPreconditionFailed, "Password contains unacceptable characters"}});
+ auto resultLogin = Login(runtime, "user12", "passwordu12*&%#");
+ UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), "Cannot find user: user12");
+ auto describe = DescribePath(runtime, TTestTxConfig::SchemeShard, "/MyRoot");
+ CheckSecurityState(describe, {.PublicKeysSize = 1, .SidsSize = 11});
+ }
+
+ {
+ CreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", "user12", "passwordu12*#");
+ auto resultLogin = Login(runtime, "user12", "passwordu12*#");
+ UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), "");
+ auto describe = DescribePath(runtime, TTestTxConfig::SchemeShard, "/MyRoot");
+ CheckSecurityState(describe, {.PublicKeysSize = 1, .SidsSize = 12});
+ CheckToken(resultLogin.token(), describe, "user12");
+ }
+ }
+
+ Y_UNIT_TEST(AccountLockoutAndAutomaticallyUnlock) {
+ TTestBasicRuntime runtime;
+ 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});
+ }
+
+ CreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", "user1", "password1");
+ NKikimrScheme::TEvLoginResult resultLogin;
+ for (size_t attempt = 0; attempt < accountLockoutConfig.GetAttemptThreshold(); attempt++) {
+ resultLogin = Login(runtime, "user1", TStringBuilder() << "wrongpassword" << attempt);
+ UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), "Invalid password");
+ }
+ resultLogin = Login(runtime, "user1", TStringBuilder() << "wrongpassword" << accountLockoutConfig.GetAttemptThreshold());
+ UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), TStringBuilder() << "User user1 is locked out");
+
+ // Also do not accept correct password
+ resultLogin = Login(runtime, "user1", "password1");
+ UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), TStringBuilder() << "User user1 is locked out");
+
+ {
+ auto describe = DescribePath(runtime, TTestTxConfig::SchemeShard, "/MyRoot");
+ CheckSecurityState(describe, {.PublicKeysSize = 1, .SidsSize = 1});
+ }
+
+ // User is blocked for 1 hour
+ runtime.AdvanceCurrentTime(TDuration::Minutes(61));
+
+ resultLogin = Login(runtime, "user1", "wrongpassword6");
+ UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), "Invalid password");
+ resultLogin = Login(runtime, "user1", "password1");
+ UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), "");
+
+ {
+ auto describe = DescribePath(runtime, TTestTxConfig::SchemeShard, "/MyRoot");
+ CheckSecurityState(describe, {.PublicKeysSize = 1, .SidsSize = 1});
+ CheckToken(resultLogin.token(), describe, "user1");
+ }
+ }
+
+ Y_UNIT_TEST(ResetFailedAttemptCount) {
+ TTestBasicRuntime runtime;
+ 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});
+ }
+
+ CreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", "user1", "password1");
+ NKikimrScheme::TEvLoginResult resultLogin;
+ for (size_t attempt = 0; attempt < accountLockoutConfig.GetAttemptThreshold() - 1; attempt++) {
+ resultLogin = Login(runtime, "user1", TStringBuilder() << "wrongpassword" << attempt);
+ UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), "Invalid password");
+ }
+
+ {
+ auto describe = DescribePath(runtime, TTestTxConfig::SchemeShard, "/MyRoot");
+ CheckSecurityState(describe, {.PublicKeysSize = 1, .SidsSize = 1});
+ }
+
+ // FailedAttemptCount will reset in 1 hour
+ runtime.AdvanceCurrentTime(TDuration::Minutes(61));
+
+ // FailedAttemptCount should be reset
+ for (size_t attempt = 0; attempt < accountLockoutConfig.GetAttemptThreshold() - 1; attempt++) {
+ resultLogin = Login(runtime, "user1", TStringBuilder() << "wrongpassword" << accountLockoutConfig.GetAttemptThreshold() + attempt);
+ UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), "Invalid password");
+ }
+ resultLogin = Login(runtime, "user1", "password1");
+ UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), "");
+
+ {
+ auto describe = DescribePath(runtime, TTestTxConfig::SchemeShard, "/MyRoot");
+ CheckSecurityState(describe, {.PublicKeysSize = 1, .SidsSize = 1});
+ CheckToken(resultLogin.token(), describe, "user1");
+ }
+ }
+
+ Y_UNIT_TEST(ChangeAccountLockoutParameters) {
+ TTestBasicRuntime runtime;
+ 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});
+ }
+
+ CreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", "user1", "password1");
+ NKikimrScheme::TEvLoginResult resultLogin;
+ for (size_t attempt = 0; attempt < accountLockoutConfig.GetAttemptThreshold(); attempt++) {
+ resultLogin = Login(runtime, "user1", TStringBuilder() << "wrongpassword" << attempt);
+ UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), "Invalid password");
+ }
+ resultLogin = Login(runtime, "user1", TStringBuilder() << "wrongpassword" << accountLockoutConfig.GetAttemptThreshold());
+ UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), TStringBuilder() << "User user1 is locked out");
+
+ {
+ auto describe = DescribePath(runtime, TTestTxConfig::SchemeShard, "/MyRoot");
+ CheckSecurityState(describe, {.PublicKeysSize = 1, .SidsSize = 1});
+ }
+
+ // user is blocked for 1 hour
+ runtime.AdvanceCurrentTime(TDuration::Minutes(61));
+
+ // Unlock user after 1 hour
+ resultLogin = Login(runtime, "user1", "password1");
+ UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), "");
+
+ {
+ auto describe = DescribePath(runtime, TTestTxConfig::SchemeShard, "/MyRoot");
+ CheckSecurityState(describe, {.PublicKeysSize = 1, .SidsSize = 1});
+ CheckToken(resultLogin.token(), describe, "user1");
+ }
+
+ size_t newAttemptThreshold = 6;
+ SetAccountLockoutParameters(runtime, TTestTxConfig::SchemeShard, {.AttemptThreshold = newAttemptThreshold, .AttemptResetDuration = "5h"});
+
+ CreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", "user2", "password2");
+ // Now user2 have 6 attempts to login
+ for (size_t attempt = 0; attempt < newAttemptThreshold; attempt++) {
+ resultLogin = Login(runtime, "user2", TStringBuilder() << "wrongpassword2" << attempt);
+ UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), "Invalid password");
+ }
+ resultLogin = Login(runtime, "user2", TStringBuilder() << "wrongpassword2" << newAttemptThreshold);
+ // User is locked out after 6 attempts
+ UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), TStringBuilder() << "User user2 is locked out");
+
+ {
+ auto describe = DescribePath(runtime, TTestTxConfig::SchemeShard, "/MyRoot");
+ CheckSecurityState(describe, {.PublicKeysSize = 1, .SidsSize = 2});
+ }
+
+ // user2 is blocked for 10 hour
+ // After 3 hours user2 must be locked out
+ runtime.AdvanceCurrentTime(TDuration::Hours(3));
+ resultLogin = Login(runtime, "user2", "wrongpassword28");
+ UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), TStringBuilder() << "User user2 is locked out");
+
+ // After 5 h 1 m user2 must be unlocked
+ runtime.AdvanceCurrentTime(TDuration::Minutes(5 * 60 + 1));
+
+ // Unlock user after 10 sec
+ resultLogin = Login(runtime, "user2", "password2");
UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), "");
+ {
+ auto describe = DescribePath(runtime, TTestTxConfig::SchemeShard, "/MyRoot");
+ CheckSecurityState(describe, {.PublicKeysSize = 1, .SidsSize = 2});
+ CheckToken(resultLogin.token(), describe, "user2");
+ }
+ }
+
+ Y_UNIT_TEST(UserStayLockedOutIfEnterValidPassword) {
+ TTestBasicRuntime runtime;
+ 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});
+ }
+
+ CreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", "user1", "password1");
+ NKikimrScheme::TEvLoginResult resultLogin;
+ for (size_t attempt = 0; attempt < accountLockoutConfig.GetAttemptThreshold(); attempt++) {
+ resultLogin = Login(runtime, "user1", TStringBuilder() << "wrongpassword" << attempt);
+ UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), "Invalid password");
+ }
+ resultLogin = Login(runtime, "user1", TStringBuilder() << "wrongpassword" << accountLockoutConfig.GetAttemptThreshold());
+ UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), TStringBuilder() << "User user1 is locked out");
+
+ // Also do not accept correct password
+ resultLogin = Login(runtime, "user1", "password1");
+ UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), TStringBuilder() << "User user1 is locked out");
+ }
+
+ void CheckUserIsLockedOutPermanently(TTestBasicRuntime& runtime) {
+ 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});
+ }
+
+ CreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", "user1", "password1");
+ NKikimrScheme::TEvLoginResult resultLogin;
+ for (size_t attempt = 0; attempt < accountLockoutConfig.GetAttemptThreshold(); attempt++) {
+ resultLogin = Login(runtime, "user1", TStringBuilder() << "wrongpassword" << attempt);
+ UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), "Invalid password");
+ }
+ resultLogin = Login(runtime, "user1", TStringBuilder() << "wrongpassword" << accountLockoutConfig.GetAttemptThreshold());
+ UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), TStringBuilder() << "User user1 is locked out");
+
+ // Also do not accept correct password
+ resultLogin = Login(runtime, "user1", "password1");
+ UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), TStringBuilder() << "User user1 is locked out");
+
+ {
+ auto describe = DescribePath(runtime, TTestTxConfig::SchemeShard, "/MyRoot");
+ CheckSecurityState(describe, {.PublicKeysSize = 1, .SidsSize = 1});
+ }
+
+ // User is blocked permanently
+ runtime.AdvanceCurrentTime(TDuration::Days(365));
+
+ // After 1 year user is locked out
+ resultLogin = Login(runtime, "user1", "wrongpassword365");
+ UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), TStringBuilder() << "User user1 is locked out");
+
+ // Also do not accept correct password
+ resultLogin = Login(runtime, "user1", "password1");
+ UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), TStringBuilder() << "User user1 is locked out");
+
+ {
+ auto describe = DescribePath(runtime, TTestTxConfig::SchemeShard, "/MyRoot");
+ CheckSecurityState(describe, {.PublicKeysSize = 1, .SidsSize = 1});
+ }
+ }
+
+ Y_UNIT_TEST(LockOutUserPermanentlyIfAttemptResetDurationIsZeroSeconds) {
+ TTestBasicRuntime runtime;
+ runtime.AddAppDataInit([] (ui32 nodeIdx, NKikimr::TAppData& appData) {
+ Y_UNUSED(nodeIdx);
+ auto accountLockout = appData.AuthConfig.MutableAccountLockout();
+ accountLockout->SetAttemptThreshold(4);
+ accountLockout->SetAttemptResetDuration("0s");
+ });
+ CheckUserIsLockedOutPermanently(runtime);
+ }
+
+ Y_UNIT_TEST(LockOutUserPermanentlyIfAttemptResetDurationCannotParse) {
+ TTestBasicRuntime runtime;
+ runtime.AddAppDataInit([] (ui32 nodeIdx, NKikimr::TAppData& appData) {
+ Y_UNUSED(nodeIdx);
+ auto accountLockout = appData.AuthConfig.MutableAccountLockout();
+ accountLockout->SetAttemptThreshold(4);
+ accountLockout->SetAttemptResetDuration("blablabla");
+ });
+ CheckUserIsLockedOutPermanently(runtime);
}
}
diff --git a/ydb/library/login/login.cpp b/ydb/library/login/login.cpp
index 0a4282d3b1..a038e2c458 100644
--- a/ydb/library/login/login.cpp
+++ b/ydb/library/login/login.cpp
@@ -320,17 +320,20 @@ TLoginProvider::TLoginUserResponse TLoginProvider::LoginUser(const TLoginUserReq
if (!request.ExternalAuth) {
auto itUser = Sids.find(request.User);
if (itUser == Sids.end() || itUser->second.Type != ESidType::USER) {
+ response.Status = TLoginUserResponse::EStatus::INVALID_USER;
response.Error = "Invalid user";
return response;
}
if (!Impl->VerifyHash(request.Password, itUser->second.Hash)) {
+ response.Status = TLoginUserResponse::EStatus::INVALID_PASSWORD;
response.Error = "Invalid password";
return response;
}
}
if (Keys.empty() || Keys.back().PrivateKey.empty()) {
+ response.Status = TLoginUserResponse::EStatus::UNAVAILABLE_KEY;
response.Error = "No key to generate token";
return response;
}
@@ -372,6 +375,7 @@ TLoginProvider::TLoginUserResponse TLoginProvider::LoginUser(const TLoginUserReq
response.Token = TString(encoded_token);
response.SanitizedToken = SanitizeJwtToken(response.Token);
+ response.Status = TLoginUserResponse::EStatus::SUCCESS;
return response;
}
diff --git a/ydb/library/login/login.h b/ydb/library/login/login.h
index 5ee672d5b5..1a1ffd121c 100644
--- a/ydb/library/login/login.h
+++ b/ydb/library/login/login.h
@@ -48,8 +48,17 @@ public:
};
struct TLoginUserResponse : TBasicResponse {
+ enum class EStatus {
+ UNSPECIFIED,
+ SUCCESS,
+ INVALID_USER,
+ INVALID_PASSWORD,
+ UNAVAILABLE_KEY
+ };
+
TString Token;
TString SanitizedToken; // Token for audit logs
+ EStatus Status = EStatus::UNSPECIFIED;
};
struct TValidateTokenRequest : TBasicRequest {
diff --git a/ydb/services/ydb/ydb_ldap_login_ut.cpp b/ydb/services/ydb/ydb_ldap_login_ut.cpp
index 0000bba5fe..206f24dad1 100644
--- a/ydb/services/ydb/ydb_ldap_login_ut.cpp
+++ b/ydb/services/ydb/ydb_ldap_login_ut.cpp
@@ -412,7 +412,7 @@ Y_UNIT_TEST_SUITE(TGRpcLdapAuthentication) {
auto factory = CreateLoginCredentialsProviderFactory({.User = login + incorrectLdapDomain, .Password = password});
TLoginClientConnection loginConnection(InitLdapSettings);
auto loginProvider = factory->CreateProvider(loginConnection.GetCoreFacility());
- UNIT_ASSERT_EXCEPTION_CONTAINS(loginProvider->GetAuthInfo(), yexception, "Invalid user");
+ UNIT_ASSERT_EXCEPTION_CONTAINS(loginProvider->GetAuthInfo(), yexception, "Cannot find user: ldapuser@ldap.domain");
loginConnection.Stop();
}
diff --git a/ydb/tests/functional/scheme_tests/canondata/tablet_scheme_tests.TestTabletSchemes.test_tablet_schemes_flat_schemeshard_/flat_schemeshard.schema b/ydb/tests/functional/scheme_tests/canondata/tablet_scheme_tests.TestTabletSchemes.test_tablet_schemes_flat_schemeshard_/flat_schemeshard.schema
index 109300a19c..cd58d3db75 100644
--- a/ydb/tests/functional/scheme_tests/canondata/tablet_scheme_tests.TestTabletSchemes.test_tablet_schemes_flat_schemeshard_/flat_schemeshard.schema
+++ b/ydb/tests/functional/scheme_tests/canondata/tablet_scheme_tests.TestTabletSchemes.test_tablet_schemes_flat_schemeshard_/flat_schemeshard.schema
@@ -6594,6 +6594,21 @@
"ColumnId": 3,
"ColumnName": "SidHash",
"ColumnType": "String"
+ },
+ {
+ "ColumnId": 4,
+ "ColumnName": "LastSuccessfulAttempt",
+ "ColumnType": "Timestamp"
+ },
+ {
+ "ColumnId": 5,
+ "ColumnName": "LastFailedAttempt",
+ "ColumnType": "Timestamp"
+ },
+ {
+ "ColumnId": 6,
+ "ColumnName": "FailedAttemptCount",
+ "ColumnType": "Uint32"
}
],
"ColumnsDropped": [],
@@ -6602,7 +6617,10 @@
"Columns": [
1,
2,
- 3
+ 3,
+ 4,
+ 5,
+ 6
],
"RoomID": 0,
"Codec": 0,
diff --git a/ydb/tests/library/common/types.py b/ydb/tests/library/common/types.py
index 05d023c35e..6758c1789b 100644
--- a/ydb/tests/library/common/types.py
+++ b/ydb/tests/library/common/types.py
@@ -248,6 +248,8 @@ class PType(AbstractTypeEnum):
Double = _ptype_from(32, float_in(-100, 100), float, proto_field='Double')
Float = _ptype_from(33, float_in(-100, 100), float, proto_field='Float')
+ Timestamp = _ptype_from(50, int_between(0, 2 ** 64 - 1), int, proto_field='Timestamp', min_value=0, max_value=2 ** 64 - 1)
+
# Rework Pair later
PairUi64Ui64 = _ptype_from(257, int_between(0, 2 ** 64 - 1), int)