diff options
author | Andrey Molotkov <[email protected]> | 2024-12-11 11:44:17 +0300 |
---|---|---|
committer | GitHub <[email protected]> | 2024-12-11 11:44:17 +0300 |
commit | e838a6204338f32ee77d077e0c4ed85a379d1d95 (patch) | |
tree | 793683b03972d48aa05c2184466e07932571ce4b | |
parent | 961df40e865111a37ebecd247c50ce316e36a402 (diff) |
fstec: Add rules for password strength (#11963)
Co-authored-by: azevaykin <[email protected]>
21 files changed, 823 insertions, 1 deletions
diff --git a/ydb/core/cms/console/console__replace_yaml_config.cpp b/ydb/core/cms/console/console__replace_yaml_config.cpp index cbb063de7c4..905c921286c 100644 --- a/ydb/core/cms/console/console__replace_yaml_config.cpp +++ b/ydb/core/cms/console/console__replace_yaml_config.cpp @@ -3,6 +3,7 @@ #include "console_audit.h" #include <ydb/core/tablet_flat/tablet_flat_executed.h> +#include <ydb/core/config/validation/validators.h> #include <ydb/library/aclib/aclib.h> #include <ydb/library/yaml_config/yaml_config.h> #include <yql/essentials/public/issue/protos/issue_severity.pb.h> @@ -100,12 +101,17 @@ public: UnknownFieldsCollector = new NYamlConfig::TBasicUnknownFieldsCollector; + std::vector<TString> errors; for (auto& [_, config] : resolved.Configs) { auto cfg = NYamlConfig::YamlToProto( config.second, true, true, UnknownFieldsCollector); + NKikimr::NConfig::EValidationResult result = NKikimr::NConfig::ValidateConfig(cfg, errors); + if (result == NKikimr::NConfig::EValidationResult::Error) { + ythrow yexception() << errors.front(); + } } const auto& deprecatedPaths = NKikimrConfig::TAppConfig::GetReservedChildrenPaths(); diff --git a/ydb/core/config/init/init_impl.h b/ydb/core/config/init/init_impl.h index 659f7ab95e1..558db1609d2 100644 --- a/ydb/core/config/init/init_impl.h +++ b/ydb/core/config/init/init_impl.h @@ -18,6 +18,7 @@ #include <ydb/core/protos/tenant_pool.pb.h> #include <ydb/core/protos/compile_service_config.pb.h> #include <ydb/core/protos/cms.pb.h> +#include <ydb/core/config/validation/validators.h> #include <ydb/library/aclib/aclib.h> #include <ydb/library/actors/core/log_iface.h> #include <ydb/library/yaml_config/yaml_config.h> @@ -1126,6 +1127,12 @@ public: TenantName = FillTenantPoolConfig(CommonAppOptions); + std::vector<TString> errors; + EValidationResult result = ValidateConfig(AppConfig, errors); + if (result == EValidationResult::Error) { + ythrow yexception() << errors.front(); + } + Logger.Out() << "configured" << Endl; FillData(CommonAppOptions); diff --git a/ydb/core/config/validation/auth_config_validator.cpp b/ydb/core/config/validation/auth_config_validator.cpp new file mode 100644 index 00000000000..7f558b37a66 --- /dev/null +++ b/ydb/core/config/validation/auth_config_validator.cpp @@ -0,0 +1,36 @@ +#include <ydb/core/protos/auth.pb.h> +#include <vector> +#include <util/generic/string.h> +#include "validators.h" + + +namespace NKikimr::NConfig { +namespace { + +EValidationResult ValidatePasswordComplexity(const NKikimrProto::TPasswordComplexity& passwordComplexity, std::vector<TString>&msg) { + size_t minCountOfRequiredChars = passwordComplexity.GetMinLowerCaseCount() + + passwordComplexity.GetMinUpperCaseCount() + + passwordComplexity.GetMinNumbersCount() + + passwordComplexity.GetMinSpecialCharsCount(); + if (passwordComplexity.GetMinLength() < minCountOfRequiredChars) { + msg = std::vector<TString>{"password_complexity: Min length of password cannot be less than " + "total min counts of lower case chars, upper case chars, numbers and special chars"}; + return EValidationResult::Error; + } + return EValidationResult::Ok; +} + +} // namespace + +EValidationResult ValidateAuthConfig(const NKikimrProto::TAuthConfig& authConfig, std::vector<TString>& msg) { + EValidationResult validatePasswordComplexityResult = ValidatePasswordComplexity(authConfig.GetPasswordComplexity(), msg); + if (validatePasswordComplexityResult == EValidationResult::Error) { + return EValidationResult::Error; + } + if (msg.size() > 0) { + return EValidationResult::Warn; + } + return EValidationResult::Ok; +} + +} // NKikimr::NConfig diff --git a/ydb/core/config/validation/auth_config_validator_ut/auth_config_validator_ut.cpp b/ydb/core/config/validation/auth_config_validator_ut/auth_config_validator_ut.cpp new file mode 100644 index 00000000000..8b68f4027aa --- /dev/null +++ b/ydb/core/config/validation/auth_config_validator_ut/auth_config_validator_ut.cpp @@ -0,0 +1,43 @@ +#include <library/cpp/testing/unittest/registar.h> +#include <ydb/core/config/validation/validators.h> +#include <ydb/core/protos/auth.pb.h> +#include <vector> + +using namespace NKikimr::NConfig; + +Y_UNIT_TEST_SUITE(AuthConfigValidation) { + Y_UNIT_TEST(AcceptValidPasswordComplexity) { + NKikimrProto::TAuthConfig authConfig; + NKikimrProto::TPasswordComplexity* validPasswordComplexity = authConfig.MutablePasswordComplexity(); + + validPasswordComplexity->SetMinLength(8); + validPasswordComplexity->SetMinLowerCaseCount(2); + validPasswordComplexity->SetMinUpperCaseCount(2); + validPasswordComplexity->SetMinNumbersCount(2); + validPasswordComplexity->SetMinSpecialCharsCount(2); + + std::vector<TString> error; + EValidationResult result = ValidateAuthConfig(authConfig, error); + UNIT_ASSERT_EQUAL(result, EValidationResult::Ok); + UNIT_ASSERT_C(error.empty(), "Should not be errors"); + } + + Y_UNIT_TEST(CannotAcceptInvalidPasswordComplexity) { + NKikimrProto::TAuthConfig authConfig; + NKikimrProto::TPasswordComplexity* invalidPasswordComplexity = authConfig.MutablePasswordComplexity(); + + // 8 < 2 + 2 + 2 + 3 + invalidPasswordComplexity->SetMinLength(8); + invalidPasswordComplexity->SetMinLowerCaseCount(2); + invalidPasswordComplexity->SetMinUpperCaseCount(2); + invalidPasswordComplexity->SetMinNumbersCount(2); + invalidPasswordComplexity->SetMinSpecialCharsCount(3); + + std::vector<TString> error; + EValidationResult result = ValidateAuthConfig(authConfig, error); + UNIT_ASSERT_EQUAL(result, EValidationResult::Error); + UNIT_ASSERT_VALUES_EQUAL(error.size(), 1); + UNIT_ASSERT_STRINGS_EQUAL(error.front(), "password_complexity: Min length of password cannot be less than " + "total min counts of lower case chars, upper case chars, numbers and special chars"); + } +} diff --git a/ydb/core/config/validation/auth_config_validator_ut/ya.make b/ydb/core/config/validation/auth_config_validator_ut/ya.make new file mode 100644 index 00000000000..beeb68c81fa --- /dev/null +++ b/ydb/core/config/validation/auth_config_validator_ut/ya.make @@ -0,0 +1,9 @@ +UNITTEST_FOR(ydb/core/config/validation) + +SRC( + auth_config_validator_ut.cpp +) + +YQL_LAST_ABI_VERSION() + +END() diff --git a/ydb/core/config/validation/validators.cpp b/ydb/core/config/validation/validators.cpp index 36391d0e9ea..7b0deb510fa 100644 --- a/ydb/core/config/validation/validators.cpp +++ b/ydb/core/config/validation/validators.cpp @@ -161,4 +161,18 @@ EValidationResult ValidateStaticGroup(const NKikimrConfig::TAppConfig& current, return EValidationResult::Ok; } +EValidationResult ValidateConfig(const NKikimrConfig::TAppConfig& config, std::vector<TString>& msg) { + if (config.HasAuthConfig()) { + NKikimr::NConfig::EValidationResult result = NKikimr::NConfig::ValidateAuthConfig(config.GetAuthConfig(), msg); + if (result == NKikimr::NConfig::EValidationResult::Error) { + return EValidationResult::Error; + } + } + if (msg.size() > 0) { + return EValidationResult::Warn; + } + + return EValidationResult::Ok; +} + } // namespace NKikimr::NConfig diff --git a/ydb/core/config/validation/validators.h b/ydb/core/config/validation/validators.h index 3e96c2c4afc..b09297cda3e 100644 --- a/ydb/core/config/validation/validators.h +++ b/ydb/core/config/validation/validators.h @@ -4,6 +4,12 @@ #include <vector> +namespace NKikimrProto { + +class TAuthConfig; + +} // NKikimrProto + namespace NKikimr::NConfig { enum class EValidationResult { @@ -32,4 +38,12 @@ EValidationResult ValidateStaticGroup( const NKikimrConfig::TAppConfig& proposed, std::vector<TString>& msg); +EValidationResult ValidateAuthConfig( + const NKikimrProto::TAuthConfig& authConfig, + std::vector<TString>& msg); + +EValidationResult ValidateConfig( + const NKikimrConfig::TAppConfig& config, + std::vector<TString>& msg); + } // namespace NKikimr::NConfig diff --git a/ydb/core/config/validation/ya.make b/ydb/core/config/validation/ya.make index db6f9c1a70f..47b4d18b9b3 100644 --- a/ydb/core/config/validation/ya.make +++ b/ydb/core/config/validation/ya.make @@ -3,6 +3,7 @@ LIBRARY() SRCS( validators.h validators.cpp + auth_config_validator.cpp ) PEERDIR( @@ -13,5 +14,5 @@ END() RECURSE_FOR_TESTS( ut + auth_config_validator_ut ) - diff --git a/ydb/core/protos/auth.proto b/ydb/core/protos/auth.proto index 6f25ed33952..e01eeda6bbb 100644 --- a/ydb/core/protos/auth.proto +++ b/ydb/core/protos/auth.proto @@ -56,6 +56,7 @@ message TAuthConfig { optional string CertificateAuthenticationDomain = 80 [default = "cert"]; optional bool EnableLoginAuthentication = 81 [default = true]; optional string NodeRegistrationToken = 82 [default = "root@builtin", (Ydb.sensitive) = true]; + optional TPasswordComplexity PasswordComplexity = 83; } message TUserRegistryConfig { @@ -122,3 +123,13 @@ message TLdapAuthentication { optional string Scheme = 11 [default = "ldap"]; optional TExtendedSettings ExtendedSettings = 12; } + +message TPasswordComplexity { + optional uint32 MinLength = 1; + optional uint32 MinLowerCaseCount = 2; + optional uint32 MinUpperCaseCount = 3; + optional uint32 MinNumbersCount = 4; + optional uint32 MinSpecialCharsCount = 5; + optional string SpecialChars = 6; + optional bool CanContainUsername = 7; +} diff --git a/ydb/core/tx/schemeshard/schemeshard_impl.cpp b/ydb/core/tx/schemeshard/schemeshard_impl.cpp index a706024ce48..fd52a583bf3 100644 --- a/ydb/core/tx/schemeshard/schemeshard_impl.cpp +++ b/ydb/core/tx/schemeshard/schemeshard_impl.cpp @@ -12,6 +12,7 @@ #include <ydb/core/base/tx_processing.h> #include <ydb/core/protos/feature_flags.pb.h> #include <ydb/core/protos/table_stats.pb.h> // for TStoragePoolsStats +#include <ydb/core/protos/auth.pb.h> #include <ydb/core/engine/mkql_proto.h> #include <ydb/core/sys_view/partition_stats/partition_stats.h> #include <ydb/core/statistics/events.h> @@ -19,6 +20,7 @@ #include <ydb/core/scheme/scheme_types_proto.h> #include <ydb/core/tx/columnshard/bg_tasks/events/events.h> #include <ydb/core/tx/scheme_board/events_schemeshard.h> +#include <ydb/library/login/password_checker/password_checker.h> #include <yql/essentials/minikql/mkql_type_ops.h> #include <yql/essentials/providers/common/proto/gateways_config.pb.h> #include <util/random/random.h> @@ -4440,6 +4442,15 @@ TSchemeShard::TSchemeShard(const TActorId &tablet, TTabletStorageInfo *info) COUNTER_PQ_STATS_WRITTEN, COUNTER_PQ_STATS_BATCH_LATENCY) , AllowDataColumnForIndexTable(0, 0, 1) + , LoginProvider(NLogin::TPasswordComplexity({ + .MinLength = AppData()->AuthConfig.GetPasswordComplexity().GetMinLength(), + .MinLowerCaseCount = AppData()->AuthConfig.GetPasswordComplexity().GetMinLowerCaseCount(), + .MinUpperCaseCount = AppData()->AuthConfig.GetPasswordComplexity().GetMinUpperCaseCount(), + .MinNumbersCount = AppData()->AuthConfig.GetPasswordComplexity().GetMinNumbersCount(), + .MinSpecialCharsCount = AppData()->AuthConfig.GetPasswordComplexity().GetMinSpecialCharsCount(), + .SpecialChars = AppData()->AuthConfig.GetPasswordComplexity().GetSpecialChars(), + .CanContainUsername = AppData()->AuthConfig.GetPasswordComplexity().GetCanContainUsername() + })) { TabletCountersPtr.Reset(new TProtobufTabletCounters< ESimpleCounters_descriptor, @@ -7128,6 +7139,10 @@ void TSchemeShard::ApplyConsoleConfigs(const NKikimrConfig::TAppConfig& appConfi ); } + if (appConfig.HasAuthConfig()) { + ConfigureLoginProvider(appConfig.GetAuthConfig(), ctx); + } + if (IsSchemeShardConfigured()) { StartStopCompactionQueues(); if (BackgroundCleaningQueue) { @@ -7321,6 +7336,32 @@ void TSchemeShard::ConfigureBackgroundCleaningQueue( << ", InflightLimit# " << cleaningConfig.InflightLimit); } +void TSchemeShard::ConfigureLoginProvider( + const ::NKikimrProto::TAuthConfig& config, + const TActorContext &ctx) +{ + const auto& passwordComplexityConfig = config.GetPasswordComplexity(); + NLogin::TPasswordComplexity passwordComplexity({ + .MinLength = passwordComplexityConfig.GetMinLength(), + .MinLowerCaseCount = passwordComplexityConfig.GetMinLowerCaseCount(), + .MinUpperCaseCount = passwordComplexityConfig.GetMinUpperCaseCount(), + .MinNumbersCount = passwordComplexityConfig.GetMinNumbersCount(), + .MinSpecialCharsCount = passwordComplexityConfig.GetMinSpecialCharsCount(), + .SpecialChars = (passwordComplexityConfig.GetSpecialChars().empty() ? NLogin::TPasswordComplexity::VALID_SPECIAL_CHARS : passwordComplexityConfig.GetSpecialChars()), + .CanContainUsername = passwordComplexityConfig.GetCanContainUsername() + }); + LoginProvider.UpdatePasswordCheckParameters(passwordComplexity); + + LOG_NOTICE_S(ctx, NKikimrServices::FLAT_TX_SCHEMESHARD, + "PasswordComplexity for LoginProvider configured: MinLength# " << passwordComplexity.MinLength + << ", MinLowerCaseCount# " << passwordComplexity.MinLowerCaseCount + << ", MinUpperCaseCount# " << passwordComplexity.MinUpperCaseCount + << ", MinNumbersCount# " << passwordComplexity.MinNumbersCount + << ", MinSpecialCharsCount# " << passwordComplexity.MinSpecialCharsCount + << ", SpecialChars# " << (passwordComplexityConfig.GetSpecialChars().empty() ? NLogin::TPasswordComplexity::VALID_SPECIAL_CHARS : passwordComplexityConfig.GetSpecialChars()) + << ", CanContainUsername# " << (passwordComplexity.CanContainUsername ? "true" : "false")); +} + 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 4f39324d948..e7152e984d0 100644 --- a/ydb/core/tx/schemeshard/schemeshard_impl.h +++ b/ydb/core/tx/schemeshard/schemeshard_impl.h @@ -497,6 +497,10 @@ public: const NKikimrConfig::TBackgroundCleaningConfig& config, const TActorContext &ctx); + void ConfigureLoginProvider( + const ::NKikimrProto::TAuthConfig& config, + const TActorContext &ctx); + void StartStopCompactionQueues(); void WaitForTableProfiles(ui64 importId, ui32 itemIdx); diff --git a/ydb/core/tx/schemeshard/ut_login/ut_login.cpp b/ydb/core/tx/schemeshard/ut_login/ut_login.cpp index d75cf0f49dd..48ffbb97660 100644 --- a/ydb/core/tx/schemeshard/ut_login/ut_login.cpp +++ b/ydb/core/tx/schemeshard/ut_login/ut_login.cpp @@ -1,6 +1,7 @@ #include <util/string/join.h> #include <ydb/library/login/login.h> +#include <ydb/library/login/password_checker/password_checker.h> #include <ydb/library/actors/http/http_proxy.h> #include <ydb/library/testlib/service_mocks/ldap_mock/ldap_simple_server.h> #include <ydb/core/tx/schemeshard/ut_helpers/helpers.h> @@ -14,6 +15,25 @@ using namespace NKikimr; using namespace NSchemeShard; using namespace NSchemeShardUT_Private; +namespace NSchemeShardUT_Private { + +void SetPasswordCheckerParameters(TTestActorRuntime &runtime, ui64 schemeShard, const NLogin::TPasswordComplexity::TInitializer& initializer) { + auto request = MakeHolder<NConsole::TEvConsole::TEvConfigNotificationRequest>(); + + ::NKikimrProto::TPasswordComplexity passwordComplexity; + passwordComplexity.SetMinLength(initializer.MinLength); + passwordComplexity.SetMinLowerCaseCount(initializer.MinLowerCaseCount); + passwordComplexity.SetMinUpperCaseCount(initializer.MinUpperCaseCount); + passwordComplexity.SetMinNumbersCount(initializer.MinNumbersCount); + passwordComplexity.SetMinSpecialCharsCount(initializer.MinSpecialCharsCount); + passwordComplexity.SetSpecialChars(initializer.SpecialChars); + passwordComplexity.SetCanContainUsername(initializer.CanContainUsername); + *request->Record.MutableConfig()->MutableAuthConfig()->MutablePasswordComplexity() = passwordComplexity; + SetConfig(runtime, schemeShard, std::move(request)); +} + +} // namespace NSchemeShardUT_Private + Y_UNIT_TEST_SUITE(TSchemeShardLoginTest) { Y_UNIT_TEST(BasicLogin) { @@ -51,6 +71,112 @@ Y_UNIT_TEST_SUITE(TSchemeShardLoginTest) { UNIT_ASSERT(describe.GetPathDescription().GetDomainDescription().HasSecurityState()); UNIT_ASSERT(describe.GetPathDescription().GetDomainDescription().GetSecurityState().PublicKeysSize() > 0); } + + Y_UNIT_TEST(ChangeAcceptablePasswordParameters) { + TTestBasicRuntime runtime; + TTestEnv env(runtime); + ui64 txId = 100; + // 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); + + // Accept password without lower case symbols + CreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", "user2", "PASSWORDU2"); + resultLogin = Login(runtime, "user2", "PASSWORDU2"); + UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), ""); + // 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"}}); + // Add lower case symbols to password + CreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", "user3", "PASswORDu3"); + resultLogin = Login(runtime, "user3", "PASswORDu3"); + UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), ""); + + // Accept password without upper case symbols + CreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", "user4", "passwordu4"); + resultLogin = Login(runtime, "user4", "passwordu4"); + UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), ""); + // 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"}}); + // Add 3 upper case symbols to password + CreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", "user5", "PASswORDu5"); + resultLogin = Login(runtime, "user5", "PASswORDu5"); + UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), ""); + + // 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(), ""); + // 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"}}); + // Password has correct length + CreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", "user7", "passwordu7"); + resultLogin = Login(runtime, "user7", "passwordu7"); + UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), ""); + + // 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(), ""); + // 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"}}); + // Password with numbers + CreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", "user9", "pas1swo5rdu9"); + resultLogin = Login(runtime, "user9", "pas1swo5rdu9"); + UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), ""); + + // 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(), ""); + // 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"}}); + // Password with special symbols + CreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", "user11", "passwordu11*&%#"); + resultLogin = Login(runtime, "user11", "passwordu11*&%#"); + UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), ""); + // 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*#"); + UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), ""); + } } namespace NSchemeShardUT_Private { diff --git a/ydb/library/login/login.cpp b/ydb/library/login/login.cpp index 62a56053242..3064f2dbcca 100644 --- a/ydb/library/login/login.cpp +++ b/ydb/library/login/login.cpp @@ -12,6 +12,8 @@ #include <deque> +#include <ydb/library/login/password_checker/password_checker.h> + #include "login.h" namespace NLogin { @@ -35,6 +37,12 @@ struct TLoginProvider::TImpl { TLoginProvider::TLoginProvider() : Impl(new TImpl()) + , PasswordChecker(TPasswordComplexity()) +{} + +TLoginProvider::TLoginProvider(const TPasswordComplexity& passwordComplexity) + : Impl(new TImpl()) + , PasswordChecker(passwordComplexity) {} TLoginProvider::~TLoginProvider() @@ -51,6 +59,13 @@ TLoginProvider::TBasicResponse TLoginProvider::CreateUser(const TCreateUserReque response.Error = "Name is not allowed"; return response; } + + TPasswordChecker::TResult passwordCheckResult = PasswordChecker.Check(request.User, request.Password); + if (!passwordCheckResult.Success) { + response.Error = passwordCheckResult.Error; + return response; + } + auto itUserCreate = Sids.emplace(request.User, TSidRecord{.Type = NLoginProto::ESidType::USER}); if (!itUserCreate.second) { if (itUserCreate.first->second.Type == ESidType::USER) { @@ -86,6 +101,12 @@ TLoginProvider::TBasicResponse TLoginProvider::ModifyUser(const TModifyUserReque return response; } + TPasswordChecker::TResult passwordCheckResult = PasswordChecker.Check(request.User, request.Password); + if (!passwordCheckResult.Success) { + response.Error = passwordCheckResult.Error; + return response; + } + TSidRecord& user = itUserModify->second; user.Hash = Impl->GenerateHash(request.Password); @@ -659,4 +680,8 @@ TString TLoginProvider::SanitizeJwtToken(const TString& token) { return TStringBuilder() << TStringBuf(token).SubString(0, signaturePos) << ".**"; // <token>.** } +void TLoginProvider::UpdatePasswordCheckParameters(const TPasswordComplexity& passwordComplexity) { + PasswordChecker.Update(passwordComplexity); +} + } diff --git a/ydb/library/login/login.h b/ydb/library/login/login.h index a182bf9b6e6..83f005f676d 100644 --- a/ydb/library/login/login.h +++ b/ydb/library/login/login.h @@ -7,6 +7,7 @@ #include <deque> #include <util/generic/string.h> #include <ydb/library/login/protos/login.pb.h> +#include <ydb/library/login/password_checker/password_checker.h> namespace NLogin { @@ -173,7 +174,10 @@ public: TRenameGroupResponse RenameGroup(const TRenameGroupRequest& request); TRemoveGroupResponse RemoveGroup(const TRemoveGroupRequest& request); + void UpdatePasswordCheckParameters(const TPasswordComplexity& passwordComplexity); + TLoginProvider(); + TLoginProvider(const TPasswordComplexity& passwordComplexity); ~TLoginProvider(); std::vector<TString> GetGroupsMembership(const TString& member); @@ -188,6 +192,8 @@ private: struct TImpl; THolder<TImpl> Impl; + + TPasswordChecker PasswordChecker; }; } diff --git a/ydb/library/login/login_ut.cpp b/ydb/library/login/login_ut.cpp index 9a63ca2eacc..248a519f7ed 100644 --- a/ydb/library/login/login_ut.cpp +++ b/ydb/library/login/login_ut.cpp @@ -1,5 +1,6 @@ #include <library/cpp/testing/unittest/registar.h> #include <util/generic/algorithm.h> +#include <ydb/library/login/password_checker/password_checker.h> #include "login.h" using namespace NLogin; @@ -79,6 +80,71 @@ Y_UNIT_TEST_SUITE(Login) { UNIT_ASSERT_VALUES_EQUAL(response3.Error, "Wrong audience"); } + Y_UNIT_TEST(TestModifyUser) { + TLoginProvider provider; + provider.Audience = "test_audience1"; + provider.RotateKeys(); + TLoginProvider::TCreateUserRequest createUser1Request { + .User = "user1", + .Password = "password1" + }; + auto createUser1Response = provider.CreateUser(createUser1Request); + UNIT_ASSERT(!createUser1Response.Error); + TLoginProvider::TLoginUserRequest loginUser1Request1 { + .User = createUser1Request.User, + .Password = createUser1Request.Password + }; + auto loginUser1Response1 = provider.LoginUser(loginUser1Request1); + UNIT_ASSERT_VALUES_EQUAL(loginUser1Response1.Error, ""); + TLoginProvider::TValidateTokenRequest validateUser1TokenRequest1 { + .Token = loginUser1Response1.Token + }; + auto validateUser1TokenResponse1 = provider.ValidateToken(validateUser1TokenRequest1); + UNIT_ASSERT_VALUES_EQUAL(validateUser1TokenResponse1.Error, ""); + UNIT_ASSERT(validateUser1TokenResponse1.User == createUser1Request.User); + + TPasswordComplexity passwordComplexity({ + .MinLength = 8, + .MinLowerCaseCount = 2, + .MinUpperCaseCount = 2, + .MinNumbersCount = 2, + .MinSpecialCharsCount = 2, + .SpecialChars = TPasswordComplexity::VALID_SPECIAL_CHARS + }); + + provider.UpdatePasswordCheckParameters(passwordComplexity); + + TLoginProvider::TModifyUserRequest modifyUser1RequestBad { + .User = createUser1Request.User, + .Password = "UserPassword1" + }; + + TLoginProvider::TBasicResponse modifyUser1ResponseBad = provider.ModifyUser(modifyUser1RequestBad); + UNIT_ASSERT(!modifyUser1ResponseBad.Error.empty()); + UNIT_ASSERT_STRINGS_EQUAL(modifyUser1ResponseBad.Error, "Incorrect password format: should contain at least 2 number, should contain at least 2 special character"); + + TLoginProvider::TModifyUserRequest modifyUser1Request { + .User = createUser1Request.User, + .Password = "paS*sw1oR#d7" + }; + + TLoginProvider::TBasicResponse modifyUser1Response = provider.ModifyUser(modifyUser1Request); + UNIT_ASSERT_VALUES_EQUAL(modifyUser1Response.Error, ""); + + TLoginProvider::TLoginUserRequest loginUser1Request2 = { + .User = modifyUser1Request.User, + .Password = modifyUser1Request.Password + }; + TLoginProvider::TLoginUserResponse loginUser1Response2 = provider.LoginUser(loginUser1Request2); + UNIT_ASSERT_VALUES_EQUAL(loginUser1Response2.Error, ""); + TLoginProvider::TValidateTokenRequest validateUser1TokenRequest2 = { + .Token = loginUser1Response2.Token + }; + TLoginProvider::TValidateTokenResponse validateUser1TokenResponse2 = provider.ValidateToken(validateUser1TokenRequest2); + UNIT_ASSERT_VALUES_EQUAL(validateUser1TokenResponse2.Error, ""); + UNIT_ASSERT(validateUser1TokenResponse2.User == createUser1Request.User); + } + Y_UNIT_TEST(TestGroups) { TLoginProvider provider; provider.Audience = "test_audience1"; diff --git a/ydb/library/login/password_checker/password_checker.cpp b/ydb/library/login/password_checker/password_checker.cpp new file mode 100644 index 00000000000..e39bb812a76 --- /dev/null +++ b/ydb/library/login/password_checker/password_checker.cpp @@ -0,0 +1,138 @@ +#include <cctype> +#include <util/string/builder.h> +#include "password_checker.h" + +namespace NLogin { + +TPasswordComplexity::TPasswordComplexity() + : SpecialChars(VALID_SPECIAL_CHARS.cbegin(), VALID_SPECIAL_CHARS.cend()) +{} + +TPasswordComplexity::TPasswordComplexity(const TInitializer& initializer) + : MinLength(initializer.MinLength) + , MinLowerCaseCount(initializer.MinLowerCaseCount) + , MinUpperCaseCount(initializer.MinUpperCaseCount) + , MinNumbersCount(initializer.MinNumbersCount) + , MinSpecialCharsCount(initializer.MinSpecialCharsCount) + , CanContainUsername(initializer.CanContainUsername) +{ + static const std::unordered_set<char> validSpecialChars(VALID_SPECIAL_CHARS.cbegin(), VALID_SPECIAL_CHARS.cend()); + for (const char ch : initializer.SpecialChars) { + if (validSpecialChars.contains(ch)) { + SpecialChars.insert(ch); + } + } +} + +bool TPasswordComplexity::IsSpecialCharValid(char ch) const { + return SpecialChars.contains(ch); +} + +const TString TPasswordComplexity::VALID_SPECIAL_CHARS = "!@#$%^&*()_+{}|<>?="; + +TPasswordChecker::TComplexityState::TComplexityState(const TPasswordComplexity& passwordComplexity) + : PasswordComplexity(passwordComplexity) +{} + +void TPasswordChecker::TComplexityState::IncLowerCaseCount() { + ++LowerCaseCount; +} + +void TPasswordChecker::TComplexityState::IncUpperCaseCount() { + ++UpperCaseCount; +} + +void TPasswordChecker::TComplexityState::IncNumbersCount() { + ++NumbersCount; +} + +void TPasswordChecker::TComplexityState::IncSpecialCharsCount() { + ++SpecialCharsCount; +} + +bool TPasswordChecker::TComplexityState::CheckLowerCaseCount() const { + return LowerCaseCount >= PasswordComplexity.MinLowerCaseCount; +} + +bool TPasswordChecker::TComplexityState::CheckUpperCaseCount() const { + return UpperCaseCount >= PasswordComplexity.MinUpperCaseCount; +} + +bool TPasswordChecker::TComplexityState::CheckNumbersCount() const { + return NumbersCount >= PasswordComplexity.MinNumbersCount; +} + +bool TPasswordChecker::TComplexityState::CheckSpecialCharsCount() const { + return SpecialCharsCount >= PasswordComplexity.MinSpecialCharsCount; +} + +TPasswordChecker::TPasswordChecker(const TPasswordComplexity& passwordComplexity) + : PasswordComplexity(passwordComplexity) +{} + +TPasswordChecker::TResult TPasswordChecker::Check(const TString& username, const TString& password) const { + if (password.empty() && PasswordComplexity.MinLength == 0) { + return {.Success = true}; + } + if (password.length() < PasswordComplexity.MinLength) { + return {.Success = false, .Error = "Password is too short"}; + } + if (!PasswordComplexity.CanContainUsername && password.Contains(username)) { + return {.Success = false, .Error = "Password must not contain user name"}; + } + + TComplexityState complexityState(PasswordComplexity); + for (const char& ch : password) { + if (std::islower(static_cast<unsigned char>(ch))) { + complexityState.IncLowerCaseCount(); + } else if (std::isupper(static_cast<unsigned char>(ch))) { + complexityState.IncUpperCaseCount(); + } else if (std::isdigit(static_cast<unsigned char>(ch))) { + complexityState.IncNumbersCount(); + } else if (PasswordComplexity.IsSpecialCharValid(ch)) { + complexityState.IncSpecialCharsCount(); + } else { + return {.Success = false, .Error = "Password contains unacceptable characters"}; + } + } + + TStringBuilder errorMessage; + errorMessage << "Incorrect password format: "; + bool hasError = false; + if (!complexityState.CheckLowerCaseCount()) { + errorMessage << "should contain at least " << PasswordComplexity.MinLowerCaseCount << " lower case character"; + hasError = true; + } + if (!complexityState.CheckUpperCaseCount()) { + if (hasError) { + errorMessage << ", "; + } + errorMessage << "should contain at least " << PasswordComplexity.MinUpperCaseCount << " upper case character"; + hasError = true; + } + if (!complexityState.CheckNumbersCount()) { + if (hasError) { + errorMessage << ", "; + } + errorMessage << "should contain at least " << PasswordComplexity.MinNumbersCount << " number"; + hasError = true; + } + if (!complexityState.CheckSpecialCharsCount()) { + if (hasError) { + errorMessage << ", "; + } + errorMessage << "should contain at least " << PasswordComplexity.MinSpecialCharsCount << " special character"; + hasError = true; + } + + if (hasError) { + return {.Success = false, .Error = errorMessage}; + } + return {.Success = true}; +} + +void TPasswordChecker::Update(const TPasswordComplexity& passwordComplexity) { + PasswordComplexity = passwordComplexity; +} + +} // NLogin diff --git a/ydb/library/login/password_checker/password_checker.h b/ydb/library/login/password_checker/password_checker.h new file mode 100644 index 00000000000..3cd25ac659a --- /dev/null +++ b/ydb/library/login/password_checker/password_checker.h @@ -0,0 +1,77 @@ +#pragma once + +#include <util/system/types.h> +#include <util/generic/string.h> +#include <unordered_set> + +namespace NLogin { + +class TPasswordComplexity { +public: + struct TInitializer { + size_t MinLength = 0; + size_t MinLowerCaseCount = 0; + size_t MinUpperCaseCount = 0; + size_t MinNumbersCount = 0; + size_t MinSpecialCharsCount = 0; + TString SpecialChars = VALID_SPECIAL_CHARS; + bool CanContainUsername = false; + }; + + static const TString VALID_SPECIAL_CHARS; + + size_t MinLength = 0; + size_t MinLowerCaseCount = 0; + size_t MinUpperCaseCount = 0; + size_t MinNumbersCount = 0; + size_t MinSpecialCharsCount = 0; + std::unordered_set<char> SpecialChars; + bool CanContainUsername = false; + + TPasswordComplexity(); + TPasswordComplexity(const TInitializer& initializer); + + bool IsSpecialCharValid(char ch) const; +}; + +class TPasswordChecker { +public: + struct TResult { + bool Success = true; + TString Error; + }; + +private: + class TComplexityState { + private: + size_t LowerCaseCount = 0; + size_t UpperCaseCount = 0; + size_t NumbersCount = 0; + size_t SpecialCharsCount = 0; + + const TPasswordComplexity& PasswordComplexity; + + public: + TComplexityState(const TPasswordComplexity& passwordComplexity); + + void IncLowerCaseCount(); + void IncUpperCaseCount(); + void IncNumbersCount(); + void IncSpecialCharsCount(); + + bool CheckLowerCaseCount() const; + bool CheckUpperCaseCount() const; + bool CheckNumbersCount() const; + bool CheckSpecialCharsCount() const; + }; + +private: + TPasswordComplexity PasswordComplexity; + +public: + TPasswordChecker(const TPasswordComplexity& passwordComplexity); + TResult Check(const TString& username, const TString& password) const; + void Update(const TPasswordComplexity& passwordComplexity); +}; + +} // NLogin diff --git a/ydb/library/login/password_checker/password_checker_ut.cpp b/ydb/library/login/password_checker/password_checker_ut.cpp new file mode 100644 index 00000000000..0f458b7c190 --- /dev/null +++ b/ydb/library/login/password_checker/password_checker_ut.cpp @@ -0,0 +1,171 @@ +#include <library/cpp/testing/unittest/registar.h> +#include <util/string/builder.h> +#include "password_checker.h" + +using namespace NLogin; + +Y_UNIT_TEST_SUITE(PasswordChecker) { + + Y_UNIT_TEST(CheckCorrectPasswordWithMaxComplexity) { + TPasswordComplexity passwordComplexity({ + .MinLength = 8, + .MinLowerCaseCount = 2, + .MinUpperCaseCount = 2, + .MinNumbersCount = 2, + .MinSpecialCharsCount = 2, + .SpecialChars = TPasswordComplexity::VALID_SPECIAL_CHARS, + .CanContainUsername = false + }); + TPasswordChecker passwordChecker(passwordComplexity); + TString username = "testuser"; + TString password = "qwer%Bs7*S4"; + TPasswordChecker::TResult result = passwordChecker.Check(username, password); + UNIT_ASSERT_C(result.Success, result.Error); + UNIT_ASSERT(result.Error.empty()); + } + + Y_UNIT_TEST(CannotAcceptTooShortPassword) { + TPasswordComplexity passwordComplexity({.MinLength = 8}); + TPasswordChecker passwordChecker(passwordComplexity); + TString username = "testuser"; + TString password = "abcd"; // Short password + TPasswordChecker::TResult result = passwordChecker.Check(username, password); + UNIT_ASSERT_C(!result.Success, "Must be error"); + UNIT_ASSERT_STRINGS_EQUAL(result.Error, "Password is too short"); + } + + Y_UNIT_TEST(PasswordCannotContainUsername) { + TPasswordComplexity passwordComplexity({.CanContainUsername = false}); + TPasswordChecker passwordChecker(passwordComplexity); + TString username = "testuser"; + TString password = "123testuserqqq"; + TPasswordChecker::TResult result = passwordChecker.Check(username, password); + UNIT_ASSERT_C(!result.Success, "Must be error"); + UNIT_ASSERT_STRINGS_EQUAL(result.Error, "Password must not contain user name"); + } + + Y_UNIT_TEST(CannotAcceptPasswordWithoutLowerCaseCharacters) { + TPasswordComplexity passwordComplexity({ + .MinLowerCaseCount = 4 + }); + TPasswordChecker passwordChecker(passwordComplexity); + TString username = "testuser"; + TString password = "12345$*QWERTY"; + TPasswordChecker::TResult result = passwordChecker.Check(username, password); + UNIT_ASSERT_C(!result.Success, "Must be error"); + UNIT_ASSERT_STRINGS_EQUAL(result.Error, TStringBuilder() << "Incorrect password format: should contain at least " + << passwordComplexity.MinLowerCaseCount + << " lower case character"); + } + + Y_UNIT_TEST(CannotAcceptPasswordWithoutUpperCaseCharacters) { + TPasswordComplexity passwordComplexity({ + .MinUpperCaseCount = 4 + }); + TPasswordChecker passwordChecker(passwordComplexity); + TString username = "testuser"; + TString password = "12345$*qwerty"; + TPasswordChecker::TResult result = passwordChecker.Check(username, password); + UNIT_ASSERT_C(!result.Success, "Must be error"); + UNIT_ASSERT_STRINGS_EQUAL(result.Error, TStringBuilder() << "Incorrect password format: should contain at least " + << passwordComplexity.MinUpperCaseCount + << " upper case character"); + } + + Y_UNIT_TEST(CannotAcceptPasswordWithoutNumbers) { + TPasswordComplexity passwordComplexity({ + .MinNumbersCount = 4 + }); + TPasswordChecker passwordChecker(passwordComplexity); + TString username = "testuser"; + TString password = "ASDF$*qwerty"; + TPasswordChecker::TResult result = passwordChecker.Check(username, password); + UNIT_ASSERT_C(!result.Success, "Must be error"); + UNIT_ASSERT_STRINGS_EQUAL(result.Error, TStringBuilder() << "Incorrect password format: should contain at least " + << passwordComplexity.MinNumbersCount + << " number"); + } + + Y_UNIT_TEST(CannotAcceptPasswordWithoutSpecialCharacters) { + TPasswordComplexity passwordComplexity({ + .MinSpecialCharsCount = 4 + }); + TPasswordChecker passwordChecker(passwordComplexity); + TString username = "testuser"; + TString password = "ASDF42qwerty"; + TPasswordChecker::TResult result = passwordChecker.Check(username, password); + UNIT_ASSERT_C(!result.Success, "Must be error"); + UNIT_ASSERT_STRINGS_EQUAL(result.Error, TStringBuilder() << "Incorrect password format: should contain at least " + << passwordComplexity.MinSpecialCharsCount + << " special character"); + } + + Y_UNIT_TEST(CannotAcceptPasswordWithInvalidCharacters) { + TPasswordComplexity passwordComplexity; + TPasswordChecker passwordChecker(passwordComplexity); + TString username = "testuser"; + TString password = "ASDF42*qwerty~~"; // ~ is invalid character + TPasswordChecker::TResult result = passwordChecker.Check(username, password); + UNIT_ASSERT_C(!result.Success, "Must be error"); + UNIT_ASSERT_STRINGS_EQUAL(result.Error, "Password contains unacceptable characters"); + } + + Y_UNIT_TEST(CannotAcceptPasswordWithoutLowerCaseAndSpecialCharacters) { + TPasswordComplexity passwordComplexity({ + .MinLowerCaseCount = 2, .MinSpecialCharsCount = 2 + }); + TPasswordChecker passwordChecker(passwordComplexity); + TString username = "testuser"; + TString password = "ASDF42Q6S7D8"; + TPasswordChecker::TResult result = passwordChecker.Check(username, password); + UNIT_ASSERT_C(!result.Success, "Must be error"); + UNIT_ASSERT_STRINGS_EQUAL(result.Error, TStringBuilder() << "Incorrect password format: should contain at least " + << passwordComplexity.MinLowerCaseCount + << " lower case character, should contain at least " + << passwordComplexity.MinSpecialCharsCount + << " special character"); + } + + Y_UNIT_TEST(AcceptPasswordWithCustomSpecialCharactersList) { + TString customSpecialCharacters = "!&*"; // Only 3 special symbols are accepted + TPasswordComplexity passwordComplexity({ + .MinSpecialCharsCount = 2, + .SpecialChars = customSpecialCharacters + }); + TPasswordChecker passwordChecker(passwordComplexity); + TString username = "testuser"; + TString correctPassword = "pass45!WOR*d"; + TPasswordChecker::TResult result = passwordChecker.Check(username, correctPassword); + UNIT_ASSERT_C(result.Success, result.Error); + UNIT_ASSERT(result.Error.empty()); + + result = passwordChecker.Check(username, "pass45!WOR$d"); // '$' incorrect symbol + UNIT_ASSERT_C(!result.Success, "Must be error"); + UNIT_ASSERT_STRINGS_EQUAL(result.Error, "Password contains unacceptable characters"); + } + + Y_UNIT_TEST(AcceptEmptyPassword) { + TPasswordComplexity passwordComplexity({ + .MinLength = 0 + }); // Enable empty password by set MinLength as 0 + TPasswordChecker passwordChecker(passwordComplexity); + TString username = "testuser"; + TString password = ""; + TPasswordChecker::TResult result = passwordChecker.Check(username, password); + UNIT_ASSERT_C(result.Success, result.Error); + UNIT_ASSERT(result.Error.empty()); + } + + Y_UNIT_TEST(CannotAcceptEmptyPassword) { + TPasswordComplexity passwordComplexity({ + .MinLength = 8 + }); // Disable empty password, min length is 8 + TPasswordChecker passwordChecker(passwordComplexity); + TString username = "testuser"; + TString password = ""; + TPasswordChecker::TResult result = passwordChecker.Check(username, password); + UNIT_ASSERT_C(!result.Success, "Must be error"); + UNIT_ASSERT_STRINGS_EQUAL(result.Error, "Password is too short"); + } + +} diff --git a/ydb/library/login/password_checker/ut/ya.make b/ydb/library/login/password_checker/ut/ya.make new file mode 100644 index 00000000000..cf54044a349 --- /dev/null +++ b/ydb/library/login/password_checker/ut/ya.make @@ -0,0 +1,9 @@ +UNITTEST_FOR(ydb/library/login/password_checker) + +PEERDIR() + +SRCS( + password_checker_ut.cpp +) + +END() diff --git a/ydb/library/login/password_checker/ya.make b/ydb/library/login/password_checker/ya.make new file mode 100644 index 00000000000..10f403ce7b7 --- /dev/null +++ b/ydb/library/login/password_checker/ya.make @@ -0,0 +1,13 @@ +LIBRARY() + +PEERDIR() + +SRCS( + password_checker.cpp +) + +END() + +RECURSE_FOR_TESTS( + ut +) diff --git a/ydb/library/login/ya.make b/ydb/library/login/ya.make index 21a013295ec..e420beb2db9 100644 --- a/ydb/library/login/ya.make +++ b/ydb/library/login/ya.make @@ -7,6 +7,7 @@ PEERDIR( library/cpp/json library/cpp/string_utils/base64 ydb/library/login/protos + ydb/library/login/password_checker ) SRCS( @@ -19,3 +20,7 @@ END() RECURSE_FOR_TESTS( ut ) + +RECURSE( + password_checker +) |