diff options
author | qrort <qrort@yandex-team.com> | 2022-11-30 23:47:12 +0300 |
---|---|---|
committer | qrort <qrort@yandex-team.com> | 2022-11-30 23:47:12 +0300 |
commit | 22f8ae0e3f5d68b92aecccdf96c1d841a0334311 (patch) | |
tree | bffa27765faf54126ad44bcafa89fadecb7a73d7 /library/cpp/tvmauth | |
parent | 332b99e2173f0425444abb759eebcb2fafaa9209 (diff) | |
download | ydb-22f8ae0e3f5d68b92aecccdf96c1d841a0334311.tar.gz |
validate canons without yatest_common
Diffstat (limited to 'library/cpp/tvmauth')
136 files changed, 16457 insertions, 0 deletions
diff --git a/library/cpp/tvmauth/README.md b/library/cpp/tvmauth/README.md new file mode 100644 index 0000000000..ec64bbbcdb --- /dev/null +++ b/library/cpp/tvmauth/README.md @@ -0,0 +1,2 @@ +This part of library contains primitives for TVM operation. +Please use high-level [TTvmClient](https://a.yandex-team.ru/arc/trunk/arcadia/library/cpp/tvmauth/client/README.md). diff --git a/library/cpp/tvmauth/checked_service_ticket.h b/library/cpp/tvmauth/checked_service_ticket.h new file mode 100644 index 0000000000..71dc48b7cb --- /dev/null +++ b/library/cpp/tvmauth/checked_service_ticket.h @@ -0,0 +1,76 @@ +#pragma once + +#include "ticket_status.h" +#include "type.h" +#include "utils.h" + +#include <util/generic/ptr.h> + +namespace NTvmAuth::NInternal { + class TCanningKnife; +} + +namespace NTvmAuth { + class TCheckedServiceTicket { + public: + class TImpl; + + TCheckedServiceTicket(THolder<TImpl> impl); + TCheckedServiceTicket(TCheckedServiceTicket&& o); + ~TCheckedServiceTicket(); + + TCheckedServiceTicket& operator=(TCheckedServiceTicket&&); + + /*! + * @return True value if ticket parsed and checked successfully + */ + explicit operator bool() const; + + /*! + * @return TTvmId of request destination + */ + TTvmId GetDst() const; + + /*! + * You should check src with your ACL + * @return TvmId of request source + */ + TTvmId GetSrc() const; + + /*! + * @return Ticket check status + */ + ETicketStatus GetStatus() const; + + /*! + * DebugInfo is human readable data for debug purposes + * @return Serialized ticket + */ + TString DebugInfo() const; + + /*! + * IssuerUID is UID of developer who is debuging something, + * so he(she) issued ServiceTicket with his(her) ssh-sign: + * it is grant_type=sshkey in tvm-api. + * https://wiki.yandex-team.ru/passport/tvm2/debug/#sxoditvapizakrytoeserviceticketami + * @return uid + */ + TMaybe<TUid> GetIssuerUid() const; + + public: // for python binding + TCheckedServiceTicket() = default; + + private: + THolder<TImpl> Impl_; + friend class NInternal::TCanningKnife; + }; + + namespace NBlackboxTvmId { + const TStringBuf Prod = "222"; + const TStringBuf Test = "224"; + const TStringBuf ProdYateam = "223"; + const TStringBuf TestYateam = "225"; + const TStringBuf Stress = "226"; + const TStringBuf Mimino = "239"; + } +} diff --git a/library/cpp/tvmauth/checked_user_ticket.h b/library/cpp/tvmauth/checked_user_ticket.h new file mode 100644 index 0000000000..16a2a6dc30 --- /dev/null +++ b/library/cpp/tvmauth/checked_user_ticket.h @@ -0,0 +1,91 @@ +#pragma once + +#include "ticket_status.h" +#include "type.h" +#include "utils.h" + +#include <util/generic/ptr.h> + +namespace NTvmAuth::NInternal { + class TCanningKnife; +} + +namespace NTvmAuth { + /*! + * BlackboxEnv describes environment of Passport: + * https://wiki.yandex-team.ru/passport/tvm2/user-ticket/#0-opredeljaemsjasokruzhenijami + */ + enum class EBlackboxEnv: ui8 { + Prod, + Test, + ProdYateam, + TestYateam, + Stress + }; + + /*! + * UserTicket contains only valid users. + * Details: https://wiki.yandex-team.ru/passport/tvm2/user-ticket/#chtoestvusertickete + */ + class TCheckedUserTicket { + public: + class TImpl; + + TCheckedUserTicket(THolder<TImpl> impl); + TCheckedUserTicket(TCheckedUserTicket&&); + ~TCheckedUserTicket(); + + TCheckedUserTicket& operator=(TCheckedUserTicket&&); + + /*! + * @return True value if ticket parsed and checked successfully + */ + explicit operator bool() const; + + /*! + * Never empty + * @return UIDs of users listed in ticket + */ + const TUids& GetUids() const; + + /*! + * Maybe 0 + * @return Default user in ticket + */ + TUid GetDefaultUid() const; + + /*! + * Scopes inherited from credential - never empty + * @return Newly constructed vector of scopes + */ + const TScopes& GetScopes() const; + + /*! + * Check if scope presented in ticket + */ + bool HasScope(TStringBuf scopeName) const; + + /*! + * @return Ticket check status + */ + ETicketStatus GetStatus() const; + + /*! + * DebugInfo is human readable data for debug purposes + * @return Serialized ticket + */ + TString DebugInfo() const; + + /*! + * Env of user + */ + EBlackboxEnv GetEnv() const; + + public: // for python binding + TCheckedUserTicket() = default; + + private: + THolder<TImpl> Impl_; + friend class NInternal::TCanningKnife; + }; +} diff --git a/library/cpp/tvmauth/client/README.md b/library/cpp/tvmauth/client/README.md new file mode 100644 index 0000000000..cda6a22d3c --- /dev/null +++ b/library/cpp/tvmauth/client/README.md @@ -0,0 +1,84 @@ +Overview +=== +This library provides ability to operate with TVM. Library is fast enough to get or check tickets for every request without burning CPU. + +[Home page of project](https://wiki.yandex-team.ru/passport/tvm2/) +You can find some examples in [here](https://a.yandex-team.ru/arc/trunk/arcadia/library/cpp/tvmauth/client/examples). + +You can ask questions: [PASSPORTDUTY](https://st.yandex-team.ru/createTicket?queue=PASSPORTDUTY&_form=77618) + +TvmClient +=== +Don't forget to collect logs from client. +___ +`TvmClient` allowes: +1. `GetServiceTicketFor()` - to fetch ServiceTicket for outgoing request +2. `CheckServiceTicket()` - to check ServiceTicket from incoming request +3. `CheckUserTicket()` - to check UserTicket from incoming request +4. `GetRoles()` - to get roles from IDM + +All methods are thread-safe. + +You should check status of `CheckedServiceTicket` or `CheckedUserTicket` for equality 'Ok'. You can get ticket fields (src/uids/scopes) only for correct ticket. Otherwise exception will be thrown. +___ +You should check status of client with `GetStatus()`: +* `OK` - nothing to do here +* `Warning` - **you should trigger your monitoring alert** + + Normal operation of TvmClient is still possible but there are problems with refreshing cache, so it is expiring. + Is tvm-api.yandex.net accessible? + Have you changed your TVM-secret or your backend (dst) deleted its TVM-client? + +* `Error` - **you should trigger your monitoring alert and close this instance for user-traffic** + + TvmClient's cache is already invalid (expired) or soon will be: you can't check valid ServiceTicket or be authenticated by your backends (dsts) + +___ +Constructor creates system thread for refreshing cache - so do not fork your proccess after creating `TTvmClient` instance. Constructor leads to network I/O. Other methods always use memory. + +Exceptions maybe thrown from constructor: +* `TRetriableException` - maybe some network trouble: you can try to create client one more time. +* `TNonRetriableException` - settings are bad: fix them. +___ +You can choose way for fetching data for your service operation: +* http://localhost:{port}/tvm - recomended way +* https://tvm-api.yandex.net + +TvmTool +------------ +`TTvmClient` uses local http-interface to get state. This interface can be provided with tvmtool (local daemon) or Qloud/YP (local http api in container). +See more: https://wiki.yandex-team.ru/passport/tvm2/tvm-daemon/. + +`TTvmClient` fetches configuration from tvmtool, so you need only to tell client how to connect to it and tell which alias of tvm id should be used for this `TvmClient` instance. + +TvmApi +------------ +First of all: please use `DiskCacheDir` - it provides reliability for your service and for tvm-api. +Please check restrictions of this field. + +Roles +=== +[Example](https://a.yandex-team.ru/arc/trunk/arcadia/library/cpp/tvmauth/client/examples/create_with_tvmapi/create.cpp?rev=r8888584#L84) + +You need to configure roles fetching +------------ +1. Enable disk cache: [DiskCacheDir](https://a.yandex-team.ru/arc/trunk/arcadia/library/cpp/tvmauth/client/misc/api/settings.h?rev=r9001419#L54) + +2. Enable ServiceTicket fetching: + [SelfTvmId](https://a.yandex-team.ru/arc/trunk/arcadia/library/cpp/tvmauth/client/misc/api/settings.h?rev=r9001419#L57) + [Secret](https://a.yandex-team.ru/arc/trunk/arcadia/library/cpp/tvmauth/client/misc/api/settings.h?rev=r9001419#L60) +3. Enable roles fetching from tirole: + [FetchRolesForIdmSystemSlug](https://a.yandex-team.ru/arc/trunk/arcadia/library/cpp/tvmauth/client/misc/api/settings.h?rev=r9001419#L78) + +You need to use roles for request check +------------ +1. Check ServiceTicket and/or UserTicket - as usual: + [CheckServiceTicket()](https://a.yandex-team.ru/arc/trunk/arcadia/library/cpp/tvmauth/client/facade.h?rev=r7890770#L91)/[CheckUserTicket()](https://a.yandex-team.ru/arc/trunk/arcadia/library/cpp/tvmauth/client/facade.h?rev=r7890770#L99) + +2. Get actual roles from `TvmClient`: [GetRoles()](https://a.yandex-team.ru/arc/trunk/arcadia/library/cpp/tvmauth/client/facade.h?rev=r7890770#L105) + +3. Use roles + - case#1: [get](https://a.yandex-team.ru/arc/trunk/arcadia/library/cpp/tvmauth/client/misc/roles/roles.h?rev=r7890770#L37-46) role list for service or user and check for the exact role you need. + - case#2: use [shortcuts](https://a.yandex-team.ru/arc/trunk/arcadia/library/cpp/tvmauth/client/misc/roles/roles.h?rev=r7890770#L50) - they are wrappers for case#1 + +4. If consumer (service or user) has required role, you can perform request. + If consumer doesn't have required role, you should show error message with useful message. diff --git a/library/cpp/tvmauth/client/client_status.cpp b/library/cpp/tvmauth/client/client_status.cpp new file mode 100644 index 0000000000..eca35ba22b --- /dev/null +++ b/library/cpp/tvmauth/client/client_status.cpp @@ -0,0 +1,6 @@ +#include "client_status.h" + +template <> +void Out<NTvmAuth::TClientStatus>(IOutputStream& out, const NTvmAuth::TClientStatus& s) { + out << s.GetCode() << ": " << s.GetLastError(); +} diff --git a/library/cpp/tvmauth/client/client_status.h b/library/cpp/tvmauth/client/client_status.h new file mode 100644 index 0000000000..bbaf29d289 --- /dev/null +++ b/library/cpp/tvmauth/client/client_status.h @@ -0,0 +1,82 @@ +#pragma once + +#include <util/generic/string.h> +#include <util/string/builder.h> + +namespace NTvmAuth { + class TClientStatus { + public: + enum ECode { + Ok, + Warning, + Error, + IncompleteTicketsSet, + }; + + TClientStatus(ECode state, TString&& lastError) + : Code_(state) + , LastError_(std::move(lastError)) + { + } + + TClientStatus() = default; + TClientStatus(const TClientStatus&) = default; + TClientStatus(TClientStatus&&) = default; + + TClientStatus& operator=(const TClientStatus&) = default; + TClientStatus& operator=(TClientStatus&&) = default; + + ECode GetCode() const { + return Code_; + } + + const TString& GetLastError() const { + return LastError_; + } + + TString CreateJugglerMessage() const { + return TStringBuilder() << GetJugglerCode() << ";TvmClient: " << LastError_ << "\n"; + } + + private: + int32_t GetJugglerCode() const { + switch (Code_) { + case ECode::Ok: + return 0; // OK juggler check state + case ECode::Warning: + case ECode::IncompleteTicketsSet: + return 1; // WARN juggler check state + case ECode::Error: + return 2; // CRIT juggler check state + } + return 2; // This should not happen, so set check state as CRIT. + } + + ECode Code_ = Ok; + TString LastError_; + }; + + static inline bool operator==(const TClientStatus& l, const TClientStatus& r) noexcept { + return l.GetCode() == r.GetCode() && l.GetLastError() == r.GetLastError(); + } + + static inline bool operator==(const TClientStatus& l, const TClientStatus::ECode r) noexcept { + return l.GetCode() == r; + } + + static inline bool operator==(const TClientStatus::ECode l, const TClientStatus& r) noexcept { + return r.GetCode() == l; + } + + static inline bool operator!=(const TClientStatus& l, const TClientStatus& r) noexcept { + return !(l == r); + } + + static inline bool operator!=(const TClientStatus& l, const TClientStatus::ECode r) noexcept { + return !(l == r); + } + + static inline bool operator!=(const TClientStatus::ECode l, const TClientStatus& r) noexcept { + return !(l == r); + } +} diff --git a/library/cpp/tvmauth/client/examples/create_with_tvmapi/create.cpp b/library/cpp/tvmauth/client/examples/create_with_tvmapi/create.cpp new file mode 100644 index 0000000000..c03a7a032f --- /dev/null +++ b/library/cpp/tvmauth/client/examples/create_with_tvmapi/create.cpp @@ -0,0 +1,102 @@ +#include <library/cpp/tvmauth/client/facade.h> + +namespace NExample { + NTvmAuth::TTvmClient CreateClientForCheckingAllTicketsAndFetchingServiceTickets() { + NTvmAuth::NTvmApi::TClientSettings setts{ + .DiskCacheDir = "/var/cache/my_service/tvm/", + .SelfTvmId = 11, + .Secret = (TStringBuf) "AAAAAAAAAAAAAAAAAAAAAA", + .FetchServiceTicketsForDstsWithAliases = { + {"bb", 224}, + {"datasync", 2000060}, + }, + .CheckServiceTickets = true, + .CheckUserTicketsWithBbEnv = NTvmAuth::EBlackboxEnv::Test, + }; + + NTvmAuth::TLoggerPtr log = MakeIntrusive<NTvmAuth::TCerrLogger>(7); + + NTvmAuth::TTvmClient c(setts, log); + + // c.CheckServiceTicket("some service ticket") + // c.CheckUserTicket("some user ticket") + // c.GetServiceTicketFor("bb") + // c.GetServiceTicketFor(224) + + return c; + } + + NTvmAuth::TTvmClient CreateClientForCheckingAllTickets() { + NTvmAuth::NTvmApi::TClientSettings setts{ + .DiskCacheDir = "/var/cache/my_service/tvm/", + .SelfTvmId = 11, + .CheckServiceTickets = true, + .CheckUserTicketsWithBbEnv = NTvmAuth::EBlackboxEnv::Test, + }; + + NTvmAuth::TLoggerPtr log = MakeIntrusive<NTvmAuth::TCerrLogger>(7); + + NTvmAuth::TTvmClient c(setts, log); + + // c.CheckServiceTicket("some service ticket") + // c.CheckUserTicket("some user ticket") + + return c; + } + + NTvmAuth::TTvmClient CreateClientForFetchingServiceTickets() { + NTvmAuth::NTvmApi::TClientSettings setts{ + .DiskCacheDir = "/var/cache/my_service/tvm/", + .SelfTvmId = 11, + .Secret = (TStringBuf) "AAAAAAAAAAAAAAAAAAAAAA", + .FetchServiceTicketsForDstsWithAliases = { + {"bb", 224}, + {"datasync", 2000060}, + }, + }; + + NTvmAuth::TLoggerPtr log = MakeIntrusive<NTvmAuth::TCerrLogger>(7); + + NTvmAuth::TTvmClient c(setts, log); + + // c.GetServiceTicketFor("bb") + // c.GetServiceTicketFor(224) + + return c; + } + + NTvmAuth::TTvmClient CreateClientForCheckingServiceTickets() { + NTvmAuth::NTvmApi::TClientSettings setts{ + .DiskCacheDir = "/var/cache/my_service/tvm/", + .SelfTvmId = 11, + .CheckServiceTickets = true, + }; + + NTvmAuth::TLoggerPtr log = MakeIntrusive<NTvmAuth::TCerrLogger>(7); + + NTvmAuth::TTvmClient c(setts, log); + + // c.CheckServiceTicket("some service ticket") + + return c; + } + + NTvmAuth::TTvmClient CreateClientForCheckingServiceTicketsWithRoles() { + NTvmAuth::NTvmApi::TClientSettings setts{ + .DiskCacheDir = "/var/cache/my_service/tvm/", + .SelfTvmId = 11, + .Secret = (TStringBuf) "AAAAAAAAAAAAAAAAAAAAAA", + .CheckServiceTickets = true, + .FetchRolesForIdmSystemSlug = "passporttestservice", + }; + + NTvmAuth::TLoggerPtr log = MakeIntrusive<NTvmAuth::TCerrLogger>(7); + + NTvmAuth::TTvmClient c(setts, log); + + // auto t = c.CheckServiceTicket("some service ticket") + // c.GetRoles()->CheckServiceRole(t, "some role"); + + return c; + } +} diff --git a/library/cpp/tvmauth/client/examples/create_with_tvmtool/create.cpp b/library/cpp/tvmauth/client/examples/create_with_tvmtool/create.cpp new file mode 100644 index 0000000000..a87d3e705d --- /dev/null +++ b/library/cpp/tvmauth/client/examples/create_with_tvmtool/create.cpp @@ -0,0 +1,34 @@ +#include <library/cpp/tvmauth/client/facade.h> + +namespace NExample { + // Possibility of using functions depends on config of tvmtool + // CheckServiceTicket + // CheckUserTicket + // GetServiceTicketFor + + NTvmAuth::TTvmClient CreateClientInQloudOrYandexDeploy() { + NTvmAuth::NTvmTool::TClientSettings setts( + "my_service" // specified in Qloud/YP/tvmtool interface + ); + + NTvmAuth::TLoggerPtr log = MakeIntrusive<NTvmAuth::TCerrLogger>(7); + + NTvmAuth::TTvmClient c(setts, log); + + return c; + } + + NTvmAuth::TTvmClient CreateClientForDevOrTests() { + NTvmAuth::NTvmTool::TClientSettings setts( + "my_service" // specified in Qloud/YP/tvmtool interface + ); + setts.SetPort(18080); + setts.SetAuthToken("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); + + NTvmAuth::TLoggerPtr log = MakeIntrusive<NTvmAuth::TCerrLogger>(7); + + NTvmAuth::TTvmClient c(setts, log); + + return c; + } +} diff --git a/library/cpp/tvmauth/client/examples/service_using_tvmtool_client/service.cpp b/library/cpp/tvmauth/client/examples/service_using_tvmtool_client/service.cpp new file mode 100644 index 0000000000..075bf0bded --- /dev/null +++ b/library/cpp/tvmauth/client/examples/service_using_tvmtool_client/service.cpp @@ -0,0 +1,84 @@ +#include "service.h" + +#include <library/cpp/tvmauth/client/facade.h> + +#include <library/cpp/cgiparam/cgiparam.h> +#include <library/cpp/http/server/response.h> +#include <library/cpp/http/simple/http_client.h> +#include <library/cpp/json/json_reader.h> + +namespace NExample { + static const TString BACK_C = "BACK_C"; + + TSomeService::TSomeService(const TConfig& cfg) + : Config_(cfg) + { + NTvmAuth::TLoggerPtr log = MakeIntrusive<NTvmAuth::TCerrLogger>(7); + + Tvm_ = MakeHolder<NTvmAuth::TTvmClient>( + NTvmAuth::NTvmTool::TClientSettings( + "my_service" // specified in Qloud/YP/tvmtool interface + ), + log); + } + + TSomeService::~TSomeService() { + } + + void TSomeService::HandleRequest(THttpInput& in, THttpOutput& out) { + auto servIt = std::find_if(in.Headers().Begin(), + in.Headers().End(), + [](const auto& h) { return h.Name() == "X-Ya-Service-Ticket"; }); + auto userIt = std::find_if(in.Headers().Begin(), + in.Headers().End(), + [](const auto& h) { return h.Name() == "X-Ya-User-Ticket"; }); + try { + if (servIt == in.Headers().End() || userIt == in.Headers().End()) { + ythrow yexception() << "Need tickets"; + } + + // WARNING: См. Здесь + NTvmAuth::TCheckedServiceTicket st = Tvm_->CheckServiceTicket(servIt->Value()); + NTvmAuth::TCheckedUserTicket ut = Tvm_->CheckUserTicket(userIt->Value()); + if (!st || !ut) { + ythrow yexception() << "Invalid tickets"; + } + + // WARNING: См. Здесь + // Ждём ABC - после их релиза эти три строки можно будет удалить + if (Config_.AllowedTvmIds.find(st.GetSrc()) == Config_.AllowedTvmIds.end()) { + ythrow yexception() << "Consumer is not allowed"; + } + + // WARNING: См. Здесь + if (!ut.HasScope("some_service:allow_secret_data")) { + ythrow yexception() << "UserTicket does not have scopes for secret data"; + } + + // Access-log + Cout << "Data fetched for: " << ut.GetDefaultUid() << Endl; + + THttpResponse resp(HTTP_OK); + resp.SetContent(GetDataFromBackendC(userIt->Value()), "text/plain"); + resp.OutTo(out); + } catch (...) { + THttpResponse resp(HTTP_BAD_REQUEST); + resp.SetContent("Request can not be performed", "text/plain"); + resp.OutTo(out); + } + + out.Finish(); + } + + TString TSomeService::GetDataFromBackendC(const TString& userTicket) { + TSimpleHttpClient cl("my_backend", // specified in Qloud/YP/tvmtool interface + 80); + TStringStream s; + cl.DoGet("/api?", + &s, + // WARNING: См. Здесь + {{"X-Ya-Service-Ticket", Tvm_->GetServiceTicketFor(BACK_C)}, + {"X-Ya-User-Ticket", userTicket}}); + return s.Str(); + } +} diff --git a/library/cpp/tvmauth/client/examples/service_using_tvmtool_client/service.h b/library/cpp/tvmauth/client/examples/service_using_tvmtool_client/service.h new file mode 100644 index 0000000000..8ff948334e --- /dev/null +++ b/library/cpp/tvmauth/client/examples/service_using_tvmtool_client/service.h @@ -0,0 +1,35 @@ +#pragma once + +#include <library/cpp/http/io/stream.h> + +#include <util/generic/ptr.h> + +#include <unordered_set> + +namespace NTvmAuth { + class TTvmClient; +} + +namespace NExample { + struct TConfig { + using TAllowedTvmIds = std::unordered_set<ui32>; + + TAllowedTvmIds AllowedTvmIds; + }; + + class TSomeService { + public: + TSomeService(const TConfig& cfg); + ~TSomeService(); + + void HandleRequest(THttpInput& in, THttpOutput& out); + + private: + TString GetDataFromBackendC(const TString& userTicket); + + private: + // WARNING: См. Здесь + TConfig Config_; + THolder<NTvmAuth::TTvmClient> Tvm_; + }; +} diff --git a/library/cpp/tvmauth/client/exception.h b/library/cpp/tvmauth/client/exception.h new file mode 100644 index 0000000000..c5faa006ad --- /dev/null +++ b/library/cpp/tvmauth/client/exception.h @@ -0,0 +1,26 @@ +#pragma once + +#include <library/cpp/tvmauth/exception.h> + +namespace NTvmAuth { + class TClientException: public TTvmException { + }; + + class TInternalException: public TTvmException { + }; + + class TRetriableException: public TClientException { + }; + class TNonRetriableException: public TClientException { + }; + + class TIllegalUsage: public TNonRetriableException { + }; + + class TBrokenTvmClientSettings: public TIllegalUsage { + }; + class TMissingServiceTicket: public TNonRetriableException { + }; + class TPermissionDenied: public TNonRetriableException { + }; +} diff --git a/library/cpp/tvmauth/client/facade.cpp b/library/cpp/tvmauth/client/facade.cpp new file mode 100644 index 0000000000..59a682678f --- /dev/null +++ b/library/cpp/tvmauth/client/facade.cpp @@ -0,0 +1,155 @@ +#include "facade.h" + +#include "misc/checker.h" +#include "misc/default_uid_checker.h" +#include "misc/getter.h" +#include "misc/src_checker.h" +#include "misc/api/threaded_updater.h" +#include "misc/tool/threaded_updater.h" + +namespace NTvmAuth { + TTvmClient::TTvmClient(const NTvmTool::TClientSettings& settings, TLoggerPtr logger) + : Updater_(NTvmTool::TThreadedUpdater::Create(settings, std::move(logger))) + , NeedService_(true) + , NeedUser_(true) + { + Y_ENSURE_EX(Updater_->GetCachedServiceContext(), TInternalException() << "Unable to get cached service context"); + Y_ENSURE_EX(Updater_->GetCachedUserContext(), TInternalException() << "Unable to get cached user context"); + + if (Updater_->GetCachedServiceTickets()) { + NeedTickets_ = true; + } + + try { + if (settings.ShouldCheckSrc && Updater_->GetRoles()) { + NeedSrcChecker_ = true; + } + + if (settings.ShouldCheckDefaultUid && Updater_->GetRoles()) { + NeedDefaultUidChecker_ = true; + } + } catch (const TBrokenTvmClientSettings&) { + // Roles are not configured + } + } + + TTvmClient::TTvmClient(const NTvmApi::TClientSettings& settings, TLoggerPtr logger) + : Updater_(NTvmApi::TThreadedUpdater::Create(settings, std::move(logger))) + , NeedService_(settings.CheckServiceTickets) + , NeedUser_(settings.CheckUserTicketsWithBbEnv) + , NeedTickets_(settings.IsServiceTicketFetchingRequired()) + , NeedSrcChecker_(settings.FetchRolesForIdmSystemSlug && settings.ShouldCheckSrc) + , NeedDefaultUidChecker_(settings.FetchRolesForIdmSystemSlug && settings.ShouldCheckDefaultUid) + { + ServiceTicketCheckFlags_.NeedDstCheck = settings.ShouldCheckDst; + if (NeedService_) { + Y_ENSURE_EX(Updater_->GetCachedServiceContext(), TInternalException() << "Unable to get cached service context"); + } + if (NeedUser_) { + Y_ENSURE_EX(Updater_->GetCachedUserContext(), TInternalException() << "Unable to get cached user context"); + } + if (NeedTickets_) { + Y_ENSURE_EX(Updater_->GetCachedServiceTickets(), TInternalException() << "Unable to get cached service tickets"); + } + if (NeedSrcChecker_) { + GetRoles(); + } + if (NeedDefaultUidChecker_) { + GetRoles(); + } + } + + TTvmClient::TTvmClient( + TAsyncUpdaterPtr updater, + const TServiceContext::TCheckFlags& serviceTicketCheckFlags) + : Updater_(std::move(updater)) + , ServiceTicketCheckFlags_(serviceTicketCheckFlags) + , NeedService_(Updater_->GetCachedServiceContext()) + , NeedUser_(Updater_->GetCachedUserContext()) + , NeedTickets_(Updater_->GetCachedServiceTickets()) + { + try { + if (Updater_->GetRoles()) { + NeedSrcChecker_ = true; + NeedDefaultUidChecker_ = true; + } + } catch (const TIllegalUsage&) { + // it is a test probably + } + } + + TTvmClient::TTvmClient(TTvmClient&& o) = default; + TTvmClient::~TTvmClient() = default; + TTvmClient& TTvmClient::operator=(TTvmClient&& o) = default; + + TClientStatus TTvmClient::GetStatus() const { + Y_ENSURE(Updater_); + return Updater_->GetStatus(); + } + + TInstant TTvmClient::GetUpdateTimeOfPublicKeys() const { + Y_ENSURE(Updater_); + return Updater_->GetUpdateTimeOfPublicKeys(); + } + + TInstant TTvmClient::GetUpdateTimeOfServiceTickets() const { + Y_ENSURE(Updater_); + return Updater_->GetUpdateTimeOfServiceTickets(); + } + + TInstant TTvmClient::GetInvalidationTimeOfPublicKeys() const { + Y_ENSURE(Updater_); + return Updater_->GetInvalidationTimeOfPublicKeys(); + } + + TInstant TTvmClient::GetInvalidationTimeOfServiceTickets() const { + Y_ENSURE(Updater_); + return Updater_->GetInvalidationTimeOfServiceTickets(); + } + + TString TTvmClient::GetServiceTicketFor(const TClientSettings::TAlias& dst) const { + Y_ENSURE_EX(NeedTickets_, TBrokenTvmClientSettings() + << "Need to enable ServiceTickets fetching"); + + TServiceTicketsPtr c = Updater_->GetCachedServiceTickets(); + return TServiceTicketGetter::GetTicket(dst, c); + } + + TString TTvmClient::GetServiceTicketFor(const TTvmId dst) const { + Y_ENSURE_EX(NeedTickets_, TBrokenTvmClientSettings() + << "Need to enable ServiceTickets fetching"); + TServiceTicketsPtr c = Updater_->GetCachedServiceTickets(); + return TServiceTicketGetter::GetTicket(dst, c); + } + + TCheckedServiceTicket TTvmClient::CheckServiceTicket(TStringBuf ticket) const { + Y_ENSURE_EX(NeedService_, TBrokenTvmClientSettings() + << "Need to use TClientSettings::EnableServiceTicketChecking()"); + + TServiceContextPtr c = Updater_->GetCachedServiceContext(); + TCheckedServiceTicket res = TServiceTicketChecker::Check(ticket, c, ServiceTicketCheckFlags_); + if (NeedSrcChecker_ && res) { + NRoles::TRolesPtr roles = Updater_->GetRoles(); + return TSrcChecker::Check(std::move(res), roles); + } + return res; + } + + TCheckedUserTicket TTvmClient::CheckUserTicket(TStringBuf ticket, TMaybe<EBlackboxEnv> overridenEnv) const { + Y_ENSURE_EX(NeedUser_, TBrokenTvmClientSettings() + << "Need to use TClientSettings::EnableUserTicketChecking()"); + + auto c = Updater_->GetCachedUserContext(overridenEnv); + TCheckedUserTicket res = TUserTicketChecker::Check(ticket, c); + if (NeedDefaultUidChecker_ && res) { + NRoles::TRolesPtr roles = Updater_->GetRoles(); + return TDefaultUidChecker::Check(std::move(res), roles); + } + return res; + } + + NRoles::TRolesPtr TTvmClient::GetRoles() const { + Y_ENSURE(Updater_); + return Updater_->GetRoles(); + } +} diff --git a/library/cpp/tvmauth/client/facade.h b/library/cpp/tvmauth/client/facade.h new file mode 100644 index 0000000000..fa459a0b55 --- /dev/null +++ b/library/cpp/tvmauth/client/facade.h @@ -0,0 +1,124 @@ +#pragma once + +#include "misc/async_updater.h" +#include "misc/api/settings.h" +#include "misc/tool/settings.h" + +#include <library/cpp/tvmauth/checked_service_ticket.h> +#include <library/cpp/tvmauth/checked_user_ticket.h> + +namespace NTvmAuth::NInternal { + class TClientCaningKnife; +} + +namespace NTvmAuth { + class TDefaultUidChecker; + class TServiceTicketGetter; + class TServiceTicketChecker; + class TSrcChecker; + class TUserTicketChecker; + + /*! + * Long lived thread-safe object for interacting with TVM. + * In 99% cases TvmClient shoud be created at service startup and live for the whole process lifetime. + */ + class TTvmClient { + public: + /*! + * Uses local http-interface to get state: http://localhost/tvm/. + * This interface can be provided with tvmtool (local daemon) or Qloud/YP (local http api in container). + * See more: https://wiki.yandex-team.ru/passport/tvm2/tvm-daemon/. + * + * Starts thread for updating of in-memory cache in background + * @param settings + * @param logger is usefull for monitoring and debuging + */ + TTvmClient(const NTvmTool::TClientSettings& settings, TLoggerPtr logger); + + /*! + * Uses general way to get state: https://tvm-api.yandex.net. + * It is not recomended for Qloud/YP. + * + * Starts thread for updating of in-memory cache in background + * Reads cache from disk if specified + * @param settings + * @param logger is usefull for monitoring and debuging + */ + TTvmClient(const NTvmApi::TClientSettings& settings, TLoggerPtr logger); + + /*! + * Feel free to use custom updating logic in tests + */ + TTvmClient( + TAsyncUpdaterPtr updater, + const TServiceContext::TCheckFlags& serviceTicketCheckFlags = {}); + + TTvmClient(TTvmClient&&); + ~TTvmClient(); + TTvmClient& operator=(TTvmClient&&); + + /*! + * You should trigger your monitoring if status is not Ok. + * It will be unable to operate if status is Error. + * Description: https://a.yandex-team.ru/arc/trunk/arcadia/library/cpp/tvmauth/client/README.md#high-level-interface + * @return Current status of client. + */ + TClientStatus GetStatus() const; + + /*! + * Some tools for monitoring + */ + + TInstant GetUpdateTimeOfPublicKeys() const; + TInstant GetUpdateTimeOfServiceTickets() const; + TInstant GetInvalidationTimeOfPublicKeys() const; + TInstant GetInvalidationTimeOfServiceTickets() const; + + /*! + * Requires fetchinig options (from TClientSettings or Qloud/YP/tvmtool settings) + * Can throw exception if cache is invalid or wrong config + * + * Alias is local label for TvmID + * which can be used to avoid this number in every checking case in code. + * @param dst + */ + TString GetServiceTicketFor(const TClientSettings::TAlias& dst) const; + TString GetServiceTicketFor(const TTvmId dst) const; + + /*! + * For TTvmApi::TClientSettings: checking must be enabled in TClientSettings + * Can throw exception if checking was not enabled in settings + * + * ServiceTicket contains src: you should check it by yourself with ACL + * @param ticket + */ + TCheckedServiceTicket CheckServiceTicket(TStringBuf ticket) const; + + /*! + * Requires blackbox enviroment (from TClientSettings or Qloud/YP/tvmtool settings) + * Can throw exception if checking was not enabled in settings + * @param ticket + * @param overrideEnv allowes you to override env from settings + */ + TCheckedUserTicket CheckUserTicket(TStringBuf ticket, TMaybe<EBlackboxEnv> overrideEnv = {}) const; + + /*! + * Under construction now. It is unusable. + * PASSP-30283 + */ + NRoles::TRolesPtr GetRoles() const; + + private: + TAsyncUpdaterPtr Updater_; + + TServiceContext::TCheckFlags ServiceTicketCheckFlags_; + + bool NeedService_ = false; + bool NeedUser_ = false; + bool NeedTickets_ = false; + bool NeedSrcChecker_ = false; + bool NeedDefaultUidChecker_ = false; + + friend class NInternal::TClientCaningKnife; + }; +} diff --git a/library/cpp/tvmauth/client/logger.cpp b/library/cpp/tvmauth/client/logger.cpp new file mode 100644 index 0000000000..bd63773cdf --- /dev/null +++ b/library/cpp/tvmauth/client/logger.cpp @@ -0,0 +1,12 @@ +#include "logger.h" + +#include <util/datetime/base.h> +#include <util/generic/string.h> + +namespace NTvmAuth { + void TCerrLogger::Log(int lvl, const TString& msg) { + if (lvl > Level_) + return; + Cerr << TInstant::Now().ToStringLocal() << " lvl=" << lvl << " msg: " << msg << "\n"; + } +} diff --git a/library/cpp/tvmauth/client/logger.h b/library/cpp/tvmauth/client/logger.h new file mode 100644 index 0000000000..6f3718a2aa --- /dev/null +++ b/library/cpp/tvmauth/client/logger.h @@ -0,0 +1,59 @@ +#pragma once + +#include <util/generic/ptr.h> + +namespace NTvmAuth { + class ILogger: public TAtomicRefCount<ILogger> { + public: + virtual ~ILogger() = default; + + void Debug(const TString& msg) { + Log(7, msg); + } + + void Info(const TString& msg) { + Log(6, msg); + } + + void Warning(const TString& msg) { + Log(4, msg); + } + + void Error(const TString& msg) { + Log(3, msg); + } + + protected: + /*! + * Log event + * @param lvl is syslog level: 0(Emergency) ... 7(Debug) + * @param msg + */ + virtual void Log(int lvl, const TString& msg) = 0; + }; + + class TCerrLogger: public ILogger { + public: + TCerrLogger(int level) + : Level_(level) + { + } + + void Log(int lvl, const TString& msg) override; + + private: + const int Level_; + }; + + using TLoggerPtr = TIntrusivePtr<ILogger>; + + class TDevNullLogger: public ILogger { + public: + static TLoggerPtr IAmBrave() { + return MakeIntrusive<TDevNullLogger>(); + } + + void Log(int, const TString&) override { + } + }; +} diff --git a/library/cpp/tvmauth/client/misc/api/dynamic_dst/tvm_client.cpp b/library/cpp/tvmauth/client/misc/api/dynamic_dst/tvm_client.cpp new file mode 100644 index 0000000000..cd6ec45406 --- /dev/null +++ b/library/cpp/tvmauth/client/misc/api/dynamic_dst/tvm_client.cpp @@ -0,0 +1,166 @@ +#include "tvm_client.h" + +#include <util/string/builder.h> + +namespace NTvmAuth::NDynamicClient { + TIntrusivePtr<TTvmClient> TTvmClient::Create(const NTvmApi::TClientSettings& settings, TLoggerPtr logger) { + Y_ENSURE_EX(logger, TNonRetriableException() << "Logger is required"); + THolder<TTvmClient> p(new TTvmClient(settings, std::move(logger))); + p->Init(); + p->StartWorker(); + return p.Release(); + } + + NThreading::TFuture<TAddResponse> TTvmClient::Add(TDsts&& dsts) { + if (dsts.empty()) { + LogDebug("Adding dst: got empty task"); + return NThreading::MakeFuture<TAddResponse>(TAddResponse{}); + } + + NThreading::TPromise<TAddResponse> promise = NThreading::NewPromise<TAddResponse>(); + + TServiceTickets::TMapIdStr requestedTicketsFromStartUpCache = GetRequestedTicketsFromStartUpCache(dsts); + + if (requestedTicketsFromStartUpCache.size() == dsts.size() && + !IsInvalid(TServiceTickets::GetInvalidationTime(requestedTicketsFromStartUpCache), TInstant::Now())) { + std::unique_lock lock(*ServiceTicketBatchUpdateMutex_); + + TPairTicketsErrors newCache; + TServiceTicketsPtr cache = GetCachedServiceTickets(); + + NTvmApi::TDstSetPtr oldDsts = GetDsts(); + std::shared_ptr<TDsts> newDsts = std::make_shared<TDsts>(oldDsts->begin(), oldDsts->end()); + + for (const auto& ticket : cache->TicketsById) { + newCache.Tickets.insert(ticket); + } + for (const auto& error : cache->ErrorsById) { + newCache.Errors.insert(error); + } + for (const auto& ticket : requestedTicketsFromStartUpCache) { + newCache.Tickets.insert(ticket); + newDsts->insert(ticket.first); + } + + UpdateServiceTicketsCache(std::move(newCache), GetStartUpCacheBornDate()); + SetDsts(std::move(newDsts)); + + lock.unlock(); + + TAddResponse response; + + for (const auto& dst : dsts) { + response.emplace(dst, TDstResponse{EDstStatus::Success, TString()}); + LogDebug(TStringBuilder() << "Got ticket from disk cache" + << ": dst=" << dst.Id << " got ticket"); + } + + promise.SetValue(std::move(response)); + return promise.GetFuture(); + } + + const size_t size = dsts.size(); + const ui64 id = ++TaskIds_; + + TaskQueue_.Enqueue(TTask{id, promise, std::move(dsts)}); + + LogDebug(TStringBuilder() << "Adding dst: got task #" << id << " with " << size << " dsts"); + return promise.GetFuture(); + } + + std::optional<TString> TTvmClient::GetOptionalServiceTicketFor(const TTvmId dst) { + TServiceTicketsPtr tickets = GetCachedServiceTickets(); + + Y_ENSURE_EX(tickets, + TBrokenTvmClientSettings() + << "Need to enable fetching of service tickets in settings"); + + auto it = tickets->TicketsById.find(dst); + if (it != tickets->TicketsById.end()) { + return it->second; + } + + it = tickets->ErrorsById.find(dst); + if (it != tickets->ErrorsById.end()) { + ythrow TMissingServiceTicket() + << "Failed to get ticket for '" << dst << "': " + << it->second; + } + + return {}; + } + + TTvmClient::TTvmClient(const NTvmApi::TClientSettings& settings, TLoggerPtr logger) + : TBase(settings, logger) + { + } + + TTvmClient::~TTvmClient() { + TBase::StopWorker(); + } + + void TTvmClient::Worker() { + TBase::Worker(); + ProcessTasks(); + } + + void TTvmClient::ProcessTasks() { + TaskQueue_.DequeueAll(&Tasks_); + if (Tasks_.empty()) { + return; + } + + TDsts required; + for (const TTask& task : Tasks_) { + for (const auto& dst : task.Dsts) { + required.insert(dst); + } + } + + TServiceTicketsPtr cache = UpdateMissingServiceTickets(required); + for (TTask& task : Tasks_) { + try { + SetResponseForTask(task, *cache); + } catch (const std::exception& e) { + LogError(TStringBuilder() + << "Adding dst: task #" << task.Id << ": exception: " << e.what()); + } catch (...) { + LogError(TStringBuilder() + << "Adding dst: task #" << task.Id << ": exception: " << CurrentExceptionMessage()); + } + } + + Tasks_.clear(); + } + + static const TString UNKNOWN = "Unknown reason"; + void TTvmClient::SetResponseForTask(TTvmClient::TTask& task, const TServiceTickets& cache) { + if (task.Promise.HasValue()) { + LogWarning(TStringBuilder() << "Adding dst: task #" << task.Id << " already has value"); + return; + } + + TAddResponse response; + + for (const auto& dst : task.Dsts) { + if (cache.TicketsById.contains(dst.Id)) { + response.emplace(dst, TDstResponse{EDstStatus::Success, TString()}); + + LogDebug(TStringBuilder() << "Adding dst: task #" << task.Id + << ": dst=" << dst.Id << " got ticket"); + continue; + } + + auto it = cache.ErrorsById.find(dst.Id); + const TString& error = it == cache.ErrorsById.end() ? UNKNOWN : it->second; + response.emplace(dst, TDstResponse{EDstStatus::Fail, error}); + + LogWarning(TStringBuilder() << "Adding dst: task #" << task.Id + << ": dst=" << dst.Id + << " failed to get ticket: " << error); + } + + LogDebug(TStringBuilder() << "Adding dst: task #" << task.Id << ": set value"); + task.Promise.SetValue(std::move(response)); + } +} diff --git a/library/cpp/tvmauth/client/misc/api/dynamic_dst/tvm_client.h b/library/cpp/tvmauth/client/misc/api/dynamic_dst/tvm_client.h new file mode 100644 index 0000000000..67eeb2618a --- /dev/null +++ b/library/cpp/tvmauth/client/misc/api/dynamic_dst/tvm_client.h @@ -0,0 +1,60 @@ +#pragma once + +#include <library/cpp/tvmauth/client/misc/api/threaded_updater.h> + +#include <library/cpp/threading/future/future.h> + +#include <util/generic/map.h> +#include <util/thread/lfqueue.h> + +#include <optional> + +namespace NTvmAuth::NDynamicClient { + enum class EDstStatus { + Success, + Fail, + }; + + struct TDstResponse { + EDstStatus Status = EDstStatus::Fail; + TString Error; + + bool operator==(const TDstResponse& o) const { + return Status == o.Status && Error == o.Error; + } + }; + + using TDsts = NTvmApi::TDstSet; + using TAddResponse = TMap<NTvmApi::TClientSettings::TDst, TDstResponse>; + + class TTvmClient: public NTvmApi::TThreadedUpdater { + public: + static TIntrusivePtr<TTvmClient> Create(const NTvmApi::TClientSettings& settings, TLoggerPtr logger); + virtual ~TTvmClient(); + + NThreading::TFuture<TAddResponse> Add(TDsts&& dsts); + std::optional<TString> GetOptionalServiceTicketFor(const TTvmId dst); + + protected: // for tests + struct TTask { + ui64 Id = 0; + NThreading::TPromise<TAddResponse> Promise; + TDsts Dsts; + }; + + using TBase = NTvmApi::TThreadedUpdater; + + protected: // for tests + TTvmClient(const NTvmApi::TClientSettings& settings, TLoggerPtr logger); + + void Worker() override; + void ProcessTasks(); + + void SetResponseForTask(TTask& task, const TServiceTickets& cache); + + private: + std::atomic<ui64> TaskIds_ = {0}; + TLockFreeQueue<TTask> TaskQueue_; + TVector<TTask> Tasks_; + }; +} diff --git a/library/cpp/tvmauth/client/misc/api/dynamic_dst/ut/tvm_client_ut.cpp b/library/cpp/tvmauth/client/misc/api/dynamic_dst/ut/tvm_client_ut.cpp new file mode 100644 index 0000000000..c633b2457a --- /dev/null +++ b/library/cpp/tvmauth/client/misc/api/dynamic_dst/ut/tvm_client_ut.cpp @@ -0,0 +1,760 @@ +#include <library/cpp/tvmauth/client/misc/api/dynamic_dst/tvm_client.h> + +#include <library/cpp/tvmauth/client/misc/disk_cache.h> + +#include <library/cpp/tvmauth/unittest.h> + +#include <library/cpp/testing/unittest/registar.h> + +#include <util/stream/file.h> +#include <util/system/fs.h> + +#include <regex> + +using namespace NTvmAuth; +using namespace NTvmAuth::NDynamicClient; + +Y_UNIT_TEST_SUITE(DynamicClient) { + static const std::regex TIME_REGEX(R"(\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d.\d{6}Z)"); + static const TString CACHE_DIR = "./tmp/"; + + static void WriteFile(TString name, TStringBuf body, TInstant time) { + NFs::Remove(CACHE_DIR + name); + TFileOutput f(CACHE_DIR + name); + f << TDiskWriter::PrepareData(time, body); + } + + static void CleanCache() { + NFs::RemoveRecursive(CACHE_DIR); + NFs::MakeDirectoryRecursive(CACHE_DIR); + } + + class TLogger: public NTvmAuth::ILogger { + public: + void Log(int lvl, const TString& msg) override { + Cout << TInstant::Now() << " lvl=" << lvl << " msg: " << msg << "\n"; + Stream << lvl << ": " << msg << Endl; + } + + TStringStream Stream; + }; + + class TOfflineUpdater: public NDynamicClient::TTvmClient { + public: + TOfflineUpdater(const NTvmApi::TClientSettings& settings, + TIntrusivePtr<TLogger> l, + bool fail = true, + std::vector<TString> tickets = {}) + : TTvmClient(settings, l) + , Fail(fail) + , Tickets(std::move(tickets)) + { + Init(); + ExpBackoff_.SetEnabled(false); + } + + NUtils::TFetchResult FetchServiceTicketsFromHttp(const TString& req) const override { + if (Fail) { + throw yexception() << "tickets: alarm"; + } + + TString response; + if (!Tickets.empty()) { + response = Tickets.front(); + Tickets.erase(Tickets.begin()); + } + + Cout << "*** FetchServiceTicketsFromHttp. request: " << req << ". response: " << response << Endl; + return {200, {}, "/2/ticket", response, ""}; + } + + NUtils::TFetchResult FetchPublicKeysFromHttp() const override { + if (Fail) { + throw yexception() << "keysalarm"; + } + Cout << "*** FetchPublicKeysFromHttp" << Endl; + return {200, {}, "/2/keys", PublicKeys, ""}; + } + + using TTvmClient::GetDsts; + using TTvmClient::ProcessTasks; + using TTvmClient::SetResponseForTask; + using TTvmClient::Worker; + + bool Fail = true; + TString PublicKeys = NUnittest::TVMKNIFE_PUBLIC_KEYS; + mutable std::vector<TString> Tickets; + }; + + Y_UNIT_TEST(StartWithIncompleteTicketsSet) { + TInstant now = TInstant::Now(); + CleanCache(); + WriteFile("./service_tickets", + R"({"19" : { "ticket" : "3:serv:CBAQ__________9_IgYIKhCUkQY:CX"}})" + "\t100500", + now); + + NTvmApi::TClientSettings s{ + .DiskCacheDir = CACHE_DIR, + .SelfTvmId = 100500, + .Secret = (TStringBuf) "qwerty", + .FetchServiceTicketsForDstsWithAliases = {{"blackbox", 19}, {"kolmo", 213}}, + .IsIncompleteTicketsSetAnError = false, + }; + + auto l = MakeIntrusive<TLogger>(); + + { + TOfflineUpdater client(s, + l, + false, + { + R"({"213" : { "error" : "some error"}})", + R"({"123" : { "ticket" : "service_ticket_3"}})", + }); + UNIT_ASSERT_VALUES_EQUAL(TClientStatus::IncompleteTicketsSet, client.GetStatus()); + UNIT_ASSERT(client.GetCachedServiceTickets()->TicketsById.contains(19)); + UNIT_ASSERT(!client.GetCachedServiceTickets()->TicketsById.contains(213)); + UNIT_ASSERT(!client.GetCachedServiceTickets()->ErrorsById.contains(19)); + UNIT_ASSERT(client.GetCachedServiceTickets()->ErrorsById.contains(213)); + + NThreading::TFuture<TAddResponse> fut = client.Add({123}); + UNIT_ASSERT_VALUES_EQUAL(TClientStatus::IncompleteTicketsSet, client.GetStatus()); + + client.Worker(); + UNIT_ASSERT_VALUES_EQUAL(TClientStatus::IncompleteTicketsSet, client.GetStatus()); + + UNIT_ASSERT(client.GetCachedServiceTickets()->TicketsById.contains(19)); + UNIT_ASSERT(!client.GetCachedServiceTickets()->TicketsById.contains(213)); + UNIT_ASSERT(client.GetCachedServiceTickets()->TicketsById.contains(123)); + UNIT_ASSERT(!client.GetCachedServiceTickets()->ErrorsById.contains(19)); + UNIT_ASSERT(client.GetCachedServiceTickets()->ErrorsById.contains(213)); + UNIT_ASSERT(!client.GetCachedServiceTickets()->ErrorsById.contains(123)); + + UNIT_ASSERT(fut.HasValue()); + TAddResponse resp{ + {123, {EDstStatus::Success, ""}}, + }; + UNIT_ASSERT_VALUES_EQUAL(resp, fut.GetValue()); + + UNIT_ASSERT(client.Tickets.empty()); + + TDsts dsts{19, 123, 213}; + UNIT_ASSERT_VALUES_EQUAL(dsts, *client.GetDsts()); + + UNIT_ASSERT_EXCEPTION_CONTAINS(client.GetOptionalServiceTicketFor(213), TMissingServiceTicket, "some error"); + } + } + + Y_UNIT_TEST(StartWithEmptyTicketsSet) { + CleanCache(); + + NTvmApi::TClientSettings s{ + .DiskCacheDir = CACHE_DIR, + .SelfTvmId = 100500, + .Secret = (TStringBuf) "qwerty", + .FetchServiceTicketsForDstsWithAliases = {{"kolmo", 213}}, + .IsIncompleteTicketsSetAnError = false, + }; + + auto l = MakeIntrusive<TLogger>(); + + { + TOfflineUpdater client(s, + l, + false, + { + R"({"213" : { "error" : "some error"}})", + R"({"123" : { "ticket" : "3:serv:CBAQ__________9_IgYIlJEGEHs:CcafYQH-FF5XaXMuJrgLZj98bIC54cs1ZkcFS9VV_9YM9iOM_0PXCtMkdg85rFjxE_BMpg7bE8ZuoqNfdw0FPt0BAKNeISwlydj4o0IjY82--LZBpP8CRn-EpAnkRaDShdlfrcF2pk1SSmEX8xdyZVQEnkUPY0cHGlFnu231vnE"}})", + }); + UNIT_ASSERT_VALUES_EQUAL(TClientStatus::IncompleteTicketsSet, client.GetStatus()); + UNIT_ASSERT(!client.GetCachedServiceTickets()->TicketsById.contains(213)); + UNIT_ASSERT(client.GetCachedServiceTickets()->ErrorsById.contains(213)); + UNIT_ASSERT_EXCEPTION_CONTAINS(client.GetOptionalServiceTicketFor(213), TMissingServiceTicket, "some error"); + + NThreading::TFuture<TAddResponse> fut = client.Add({123}); + UNIT_ASSERT_VALUES_EQUAL(TClientStatus::IncompleteTicketsSet, client.GetStatus()); + + client.Worker(); + UNIT_ASSERT_VALUES_EQUAL(TClientStatus::IncompleteTicketsSet, client.GetStatus()); + + UNIT_ASSERT(!client.GetCachedServiceTickets()->TicketsById.contains(213)); + UNIT_ASSERT(client.GetCachedServiceTickets()->TicketsById.contains(123)); + UNIT_ASSERT(client.GetCachedServiceTickets()->ErrorsById.contains(213)); + UNIT_ASSERT(!client.GetCachedServiceTickets()->ErrorsById.contains(123)); + + UNIT_ASSERT(fut.HasValue()); + TAddResponse resp{ + {123, {EDstStatus::Success, ""}}, + }; + UNIT_ASSERT_VALUES_EQUAL(resp, fut.GetValue()); + + UNIT_ASSERT(client.Tickets.empty()); + + TDsts dsts{123, 213}; + UNIT_ASSERT_VALUES_EQUAL(dsts, *client.GetDsts()); + + UNIT_ASSERT_EXCEPTION_CONTAINS(client.GetOptionalServiceTicketFor(213), TMissingServiceTicket, "some error"); + } + }; + Y_UNIT_TEST(StartWithIncompleteCacheAndAdd) { + TInstant now = TInstant::Now(); + CleanCache(); + WriteFile("./service_tickets", + R"({"19" : { "ticket" : "3:serv:CBAQ__________9_IgYIKhCUkQY:CX"}})" + "\t100500", + now); + + NTvmApi::TClientSettings s{ + .DiskCacheDir = CACHE_DIR, + .SelfTvmId = 100500, + .Secret = (TStringBuf) "qwerty", + .FetchServiceTicketsForDstsWithAliases = {{"blackbox", 19}, {"kolmo", 213}}, + }; + + auto l = MakeIntrusive<TLogger>(); + + UNIT_ASSERT_EXCEPTION_CONTAINS(TOfflineUpdater(s, l), + TRetriableException, + "Failed to start TvmClient. You can retry: ServiceTickets: tickets: alarm"); + UNIT_ASSERT_VALUES_EQUAL( + TStringBuilder() + << "6: File './tmp/service_tickets' was successfully read\n" + << "6: Got 1 service ticket(s) from disk\n" + << "6: Cache was updated with 1 service ticket(s): XXXXXXXXXXX\n" + << "7: File './tmp/retry_settings' does not exist\n" + << "4: Failed to get ServiceTickets: tickets: alarm\n" + << "4: Failed to get ServiceTickets: tickets: alarm\n" + << "4: Failed to get ServiceTickets: tickets: alarm\n" + << "4: Failed to update service tickets: tickets: alarm\n", + std::regex_replace(std::string(l->Stream.Str()), TIME_REGEX, "XXXXXXXXXXX")); + l->Stream.Str().clear(); + + { + TOfflineUpdater client(s, + l, + false, + { + R"({"213" : { "ticket" : "service_ticket_2"}})", + R"({"123" : { "ticket" : "service_ticket_3"}})", + }); + UNIT_ASSERT_VALUES_EQUAL(TClientStatus::Ok, client.GetStatus()); + UNIT_ASSERT(client.GetCachedServiceTickets()->TicketsById.contains(19)); + UNIT_ASSERT(client.GetCachedServiceTickets()->TicketsById.contains(213)); + UNIT_ASSERT(!client.GetCachedServiceTickets()->ErrorsById.contains(19)); + UNIT_ASSERT(!client.GetCachedServiceTickets()->ErrorsById.contains(213)); + + NThreading::TFuture<TAddResponse> fut = client.Add({123}); + UNIT_ASSERT_VALUES_EQUAL(TClientStatus::Ok, client.GetStatus()); + + client.Worker(); + UNIT_ASSERT_VALUES_EQUAL(TClientStatus::Ok, client.GetStatus()); + + UNIT_ASSERT(client.GetCachedServiceTickets()->TicketsById.contains(19)); + UNIT_ASSERT(client.GetCachedServiceTickets()->TicketsById.contains(213)); + UNIT_ASSERT(client.GetCachedServiceTickets()->TicketsById.contains(123)); + UNIT_ASSERT(!client.GetCachedServiceTickets()->ErrorsById.contains(19)); + UNIT_ASSERT(!client.GetCachedServiceTickets()->ErrorsById.contains(213)); + UNIT_ASSERT(!client.GetCachedServiceTickets()->ErrorsById.contains(123)); + + UNIT_ASSERT(fut.HasValue()); + TAddResponse resp{ + {123, {EDstStatus::Success, ""}}, + }; + UNIT_ASSERT_VALUES_EQUAL(resp, fut.GetValue()); + + UNIT_ASSERT(client.Tickets.empty()); + + TDsts dsts{19, 123, 213}; + UNIT_ASSERT_VALUES_EQUAL(dsts, *client.GetDsts()); + } + + UNIT_ASSERT_VALUES_EQUAL( + TStringBuilder() + << "6: File './tmp/service_tickets' was successfully read\n" + << "6: Got 1 service ticket(s) from disk\n" + << "6: Cache was updated with 1 service ticket(s): " << TInstant::Seconds(now.Seconds()) << "\n" + << "7: File './tmp/retry_settings' does not exist\n" + << "7: Response with service tickets for 1 destination(s) was successfully fetched from https://tvm-api.yandex.net\n" + << "7: Got responses with service tickets with 1 pages for 1 destination(s)\n" + << "6: Cache was partly updated with 1 service ticket(s). total: 2\n" + << "6: File './tmp/service_tickets' was successfully written\n" + << "7: Adding dst: got task #1 with 1 dsts\n" + << "7: Response with service tickets for 1 destination(s) was successfully fetched from https://tvm-api.yandex.net\n" + << "7: Got responses with service tickets with 1 pages for 1 destination(s)\n" + << "6: Cache was partly updated with 1 service ticket(s). total: 3\n" + << "6: File './tmp/service_tickets' was successfully written\n" + << "7: Adding dst: task #1: dst=123 got ticket\n" + << "7: Adding dst: task #1: set value\n", + l->Stream.Str()); + } + + Y_UNIT_TEST(StartWithCacheAndAdd) { + TInstant now = TInstant::Now(); + CleanCache(); + WriteFile("./service_tickets", + R"({"19" : { "ticket" : "3:serv:CBAQ__________9_IgYIKhCUkQY:CX"}})" + "\t100500", + now); + + NTvmApi::TClientSettings s{ + .DiskCacheDir = CACHE_DIR, + .SelfTvmId = 100500, + .Secret = (TStringBuf) "qwerty", + .FetchServiceTicketsForDstsWithAliases = {{"blackbox", 19}}, + .IsIncompleteTicketsSetAnError = false, + }; + + auto l = MakeIntrusive<TLogger>(); + { + TOfflineUpdater client(s, l); + UNIT_ASSERT_VALUES_EQUAL(TClientStatus::Ok, client.GetStatus()); + UNIT_ASSERT(client.GetCachedServiceTickets()->TicketsById.contains(19)); + UNIT_ASSERT(!client.GetCachedServiceTickets()->ErrorsById.contains(19)); + + client.Fail = false; + client.Tickets = { + R"({"123" : { "ticket" : "service_ticket_3"}, "213" : { "ticket" : "service_ticket_2"}})", + }; + NThreading::TFuture<TAddResponse> fut = client.Add({123, 213}); + + client.Worker(); + UNIT_ASSERT_VALUES_EQUAL(TClientStatus::Ok, client.GetStatus()); + + UNIT_ASSERT(client.GetCachedServiceTickets()->TicketsById.contains(19)); + UNIT_ASSERT(client.GetCachedServiceTickets()->TicketsById.contains(213)); + UNIT_ASSERT(client.GetCachedServiceTickets()->TicketsById.contains(123)); + UNIT_ASSERT(!client.GetCachedServiceTickets()->ErrorsById.contains(19)); + UNIT_ASSERT(!client.GetCachedServiceTickets()->ErrorsById.contains(213)); + UNIT_ASSERT(!client.GetCachedServiceTickets()->ErrorsById.contains(123)); + + UNIT_ASSERT(fut.HasValue()); + TAddResponse resp{ + {123, {EDstStatus::Success, ""}}, + {213, {EDstStatus::Success, ""}}, + }; + UNIT_ASSERT_VALUES_EQUAL(resp, fut.GetValue()); + + UNIT_ASSERT(client.Tickets.empty()); + + TDsts dsts{19, 123, 213}; + UNIT_ASSERT_VALUES_EQUAL(dsts, *client.GetDsts()); + } + + UNIT_ASSERT_VALUES_EQUAL( + TStringBuilder() + << "6: File './tmp/service_tickets' was successfully read\n" + << "6: Got 1 service ticket(s) from disk\n" + << "6: Cache was updated with 1 service ticket(s): " << TInstant::Seconds(now.Seconds()) << "\n" + << "7: File './tmp/retry_settings' does not exist\n" + << "7: Adding dst: got task #1 with 2 dsts\n" + << "7: Response with service tickets for 2 destination(s) was successfully fetched from https://tvm-api.yandex.net\n" + << "7: Got responses with service tickets with 1 pages for 2 destination(s)\n" + << "6: Cache was partly updated with 2 service ticket(s). total: 3\n" + << "6: File './tmp/service_tickets' was successfully written\n" + << "7: Adding dst: task #1: dst=123 got ticket\n" + << "7: Adding dst: task #1: dst=213 got ticket\n" + << "7: Adding dst: task #1: set value\n", + l->Stream.Str()); + } + + Y_UNIT_TEST(StartWithCacheAndAddSeveral) { + TInstant now = TInstant::Now(); + CleanCache(); + WriteFile("./service_tickets", + R"({"19" : { "ticket" : "3:serv:CBAQ__________9_IgYIKhCUkQY:CX"}})" + "\t100500", + now); + + NTvmApi::TClientSettings s{ + .DiskCacheDir = CACHE_DIR, + .SelfTvmId = 100500, + .Secret = (TStringBuf) "qwerty", + .FetchServiceTicketsForDstsWithAliases = {{"blackbox", 19}}, + }; + + auto l = MakeIntrusive<TLogger>(); + { + TOfflineUpdater client(s, l); + UNIT_ASSERT_VALUES_EQUAL(TClientStatus::Ok, client.GetStatus()); + UNIT_ASSERT(client.GetCachedServiceTickets()->TicketsById.contains(19)); + UNIT_ASSERT(!client.GetCachedServiceTickets()->ErrorsById.contains(19)); + + client.Fail = false; + client.Tickets = { + R"({"123" : { "ticket" : "service_ticket_3"}, "213" : { "ticket" : "service_ticket_2"}})", + }; + NThreading::TFuture<TAddResponse> fut1 = client.Add({123}); + NThreading::TFuture<TAddResponse> fut2 = client.Add({213}); + + client.Worker(); + UNIT_ASSERT_VALUES_EQUAL(TClientStatus::Ok, client.GetStatus()); + + UNIT_ASSERT(client.GetCachedServiceTickets()->TicketsById.contains(19)); + UNIT_ASSERT(client.GetCachedServiceTickets()->TicketsById.contains(213)); + UNIT_ASSERT(client.GetCachedServiceTickets()->TicketsById.contains(123)); + UNIT_ASSERT(!client.GetCachedServiceTickets()->ErrorsById.contains(19)); + UNIT_ASSERT(!client.GetCachedServiceTickets()->ErrorsById.contains(213)); + UNIT_ASSERT(!client.GetCachedServiceTickets()->ErrorsById.contains(123)); + + UNIT_ASSERT(fut1.HasValue()); + TAddResponse resp1{ + {123, {EDstStatus::Success, ""}}, + }; + UNIT_ASSERT_VALUES_EQUAL(resp1, fut1.GetValue()); + + UNIT_ASSERT(fut2.HasValue()); + TAddResponse resp2{ + {213, {EDstStatus::Success, ""}}, + }; + UNIT_ASSERT_VALUES_EQUAL(resp2, fut2.GetValue()); + + UNIT_ASSERT(client.Tickets.empty()); + + TDsts dsts{19, 123, 213}; + UNIT_ASSERT_VALUES_EQUAL(dsts, *client.GetDsts()); + } + + UNIT_ASSERT_VALUES_EQUAL( + TStringBuilder() + << "6: File './tmp/service_tickets' was successfully read\n" + << "6: Got 1 service ticket(s) from disk\n" + << "6: Cache was updated with 1 service ticket(s): " << TInstant::Seconds(now.Seconds()) << "\n" + << "7: File './tmp/retry_settings' does not exist\n" + << "7: Adding dst: got task #1 with 1 dsts\n" + << "7: Adding dst: got task #2 with 1 dsts\n" + << "7: Response with service tickets for 2 destination(s) was successfully fetched from https://tvm-api.yandex.net\n" + << "7: Got responses with service tickets with 1 pages for 2 destination(s)\n" + << "6: Cache was partly updated with 2 service ticket(s). total: 3\n" + << "6: File './tmp/service_tickets' was successfully written\n" + << "7: Adding dst: task #1: dst=123 got ticket\n" + << "7: Adding dst: task #1: set value\n" + << "7: Adding dst: task #2: dst=213 got ticket\n" + << "7: Adding dst: task #2: set value\n", + l->Stream.Str()); + } + + Y_UNIT_TEST(StartWithCacheAndAddSeveralWithErrors) { + TInstant now = TInstant::Now(); + CleanCache(); + WriteFile("./service_tickets", + R"({"19" : { "ticket" : "3:serv:CBAQ__________9_IgYIKhCUkQY:CX"}})" + "\t100500", + now); + + NTvmApi::TClientSettings s{ + .DiskCacheDir = CACHE_DIR, + .SelfTvmId = 100500, + .Secret = (TStringBuf) "qwerty", + .FetchServiceTicketsForDstsWithAliases = {{"blackbox", 19}}, + }; + + auto l = MakeIntrusive<TLogger>(); + { + TOfflineUpdater client(s, l); + UNIT_ASSERT_VALUES_EQUAL(TClientStatus::Ok, client.GetStatus()); + UNIT_ASSERT(client.GetCachedServiceTickets()->TicketsById.contains(19)); + UNIT_ASSERT(!client.GetCachedServiceTickets()->ErrorsById.contains(19)); + + UNIT_ASSERT(client.GetOptionalServiceTicketFor(19)); + UNIT_ASSERT_VALUES_EQUAL("3:serv:CBAQ__________9_IgYIKhCUkQY:CX", + *client.GetOptionalServiceTicketFor(19)); + UNIT_ASSERT(!client.GetOptionalServiceTicketFor(456)); + + client.Fail = false; + client.Tickets = { + R"({ + "123" : { "ticket" : "service_ticket_3"}, + "213" : { "ticket" : "service_ticket_2"}, + "456" : { "error" : "error_3"} + })", + }; + NThreading::TFuture<TAddResponse> fut1 = client.Add({123, 213}); + NThreading::TFuture<TAddResponse> fut2 = client.Add({213, 456}); + + client.Worker(); + UNIT_ASSERT_VALUES_EQUAL(TClientStatus::Ok, client.GetStatus()); + + UNIT_ASSERT(client.GetCachedServiceTickets()->TicketsById.contains(19)); + UNIT_ASSERT(client.GetCachedServiceTickets()->TicketsById.contains(213)); + UNIT_ASSERT(client.GetCachedServiceTickets()->TicketsById.contains(123)); + UNIT_ASSERT(!client.GetCachedServiceTickets()->TicketsById.contains(456)); + UNIT_ASSERT(!client.GetCachedServiceTickets()->ErrorsById.contains(19)); + UNIT_ASSERT(!client.GetCachedServiceTickets()->ErrorsById.contains(213)); + UNIT_ASSERT(!client.GetCachedServiceTickets()->ErrorsById.contains(123)); + UNIT_ASSERT(client.GetCachedServiceTickets()->ErrorsById.contains(456)); + + UNIT_ASSERT(client.GetOptionalServiceTicketFor(19)); + UNIT_ASSERT_VALUES_EQUAL("3:serv:CBAQ__________9_IgYIKhCUkQY:CX", + *client.GetOptionalServiceTicketFor(19)); + UNIT_ASSERT_EXCEPTION_CONTAINS(client.GetOptionalServiceTicketFor(456), + TMissingServiceTicket, + "Failed to get ticket for '456': error_3"); + + UNIT_ASSERT(fut1.HasValue()); + TAddResponse resp1{ + {123, {EDstStatus::Success, ""}}, + {213, {EDstStatus::Success, ""}}, + }; + UNIT_ASSERT_VALUES_EQUAL(resp1, fut1.GetValue()); + + UNIT_ASSERT(fut2.HasValue()); + TAddResponse resp2{ + {213, {EDstStatus::Success, ""}}, + {456, {EDstStatus::Fail, "error_3"}}, + }; + UNIT_ASSERT_VALUES_EQUAL(resp2, fut2.GetValue()); + + UNIT_ASSERT(client.Tickets.empty()); + + TDsts dsts{19, 123, 213}; + UNIT_ASSERT_VALUES_EQUAL(dsts, *client.GetDsts()); + } + + UNIT_ASSERT_VALUES_EQUAL( + TStringBuilder() + << "6: File './tmp/service_tickets' was successfully read\n" + << "6: Got 1 service ticket(s) from disk\n" + << "6: Cache was updated with 1 service ticket(s): " << TInstant::Seconds(now.Seconds()) << "\n" + << "7: File './tmp/retry_settings' does not exist\n" + << "7: Adding dst: got task #1 with 2 dsts\n" + << "7: Adding dst: got task #2 with 2 dsts\n" + << "7: Response with service tickets for 3 destination(s) was successfully fetched from https://tvm-api.yandex.net\n" + << "7: Got responses with service tickets with 1 pages for 3 destination(s)\n" + << "3: Failed to get service ticket for dst=456: error_3\n" + << "6: Cache was partly updated with 2 service ticket(s). total: 3\n" + << "6: File './tmp/service_tickets' was successfully written\n" + << "7: Adding dst: task #1: dst=123 got ticket\n" + << "7: Adding dst: task #1: dst=213 got ticket\n" + << "7: Adding dst: task #1: set value\n" + << "7: Adding dst: task #2: dst=213 got ticket\n" + << "4: Adding dst: task #2: dst=456 failed to get ticket: error_3\n" + << "7: Adding dst: task #2: set value\n", + l->Stream.Str()); + } + + Y_UNIT_TEST(WithException) { + TInstant now = TInstant::Now(); + CleanCache(); + WriteFile("./service_tickets", + R"({"19" : { "ticket" : "3:serv:CBAQ__________9_IgYIKhCUkQY:CX"}})" + "\t100500", + now); + + NTvmApi::TClientSettings s{ + .DiskCacheDir = CACHE_DIR, + .SelfTvmId = 100500, + .Secret = (TStringBuf) "qwerty", + .FetchServiceTicketsForDstsWithAliases = {{"blackbox", 19}}, + }; + + auto l = MakeIntrusive<TLogger>(); + { + TOfflineUpdater client(s, l); + UNIT_ASSERT_VALUES_EQUAL(TClientStatus::Ok, client.GetStatus()); + UNIT_ASSERT(client.GetCachedServiceTickets()->TicketsById.contains(19)); + UNIT_ASSERT(!client.GetCachedServiceTickets()->ErrorsById.contains(19)); + + client.Fail = false; + client.Tickets = { + R"({ + "123" : { "ticket" : "service_ticket_3"}, + "213" : { "ticket" : "service_ticket_2"}, + "456" : { "error" : "error_3"}, + "789" : { "ticket" : "service_ticket_4"} + })", + }; + NThreading::TFuture<TAddResponse> fut1 = client.Add({123, 213}); + NThreading::TFuture<TAddResponse> fut2 = client.Add({213, 456}); + NThreading::TFuture<TAddResponse> fut3 = client.Add({789}); + + fut2.Subscribe([](const auto&) { + throw yexception() << "planed exc"; + }); + fut3.Subscribe([](const auto&) { + throw 5; + }); + + UNIT_ASSERT_NO_EXCEPTION(client.Worker()); + UNIT_ASSERT_VALUES_EQUAL(TClientStatus::Ok, client.GetStatus()); + + UNIT_ASSERT(client.GetCachedServiceTickets()->TicketsById.contains(19)); + UNIT_ASSERT(client.GetCachedServiceTickets()->TicketsById.contains(213)); + UNIT_ASSERT(client.GetCachedServiceTickets()->TicketsById.contains(123)); + UNIT_ASSERT(!client.GetCachedServiceTickets()->TicketsById.contains(456)); + UNIT_ASSERT(!client.GetCachedServiceTickets()->ErrorsById.contains(19)); + UNIT_ASSERT(!client.GetCachedServiceTickets()->ErrorsById.contains(213)); + UNIT_ASSERT(!client.GetCachedServiceTickets()->ErrorsById.contains(123)); + UNIT_ASSERT(client.GetCachedServiceTickets()->ErrorsById.contains(456)); + + UNIT_ASSERT(fut1.HasValue()); + TAddResponse resp1{ + {123, {EDstStatus::Success, ""}}, + {213, {EDstStatus::Success, ""}}, + }; + UNIT_ASSERT_VALUES_EQUAL(resp1, fut1.GetValue()); + + UNIT_ASSERT(fut2.HasValue()); + TAddResponse resp2{ + {213, {EDstStatus::Success, ""}}, + {456, {EDstStatus::Fail, "error_3"}}, + }; + UNIT_ASSERT_VALUES_EQUAL(resp2, fut2.GetValue()); + + UNIT_ASSERT(fut3.HasValue()); + TAddResponse resp3{ + {789, {EDstStatus::Success, ""}}, + }; + UNIT_ASSERT_VALUES_EQUAL(resp3, fut3.GetValue()); + + UNIT_ASSERT(client.Tickets.empty()); + + TDsts dsts{19, 123, 213, 789}; + UNIT_ASSERT_VALUES_EQUAL(dsts, *client.GetDsts()); + } + + UNIT_ASSERT_VALUES_EQUAL( + TStringBuilder() + << "6: File './tmp/service_tickets' was successfully read\n" + << "6: Got 1 service ticket(s) from disk\n" + << "6: Cache was updated with 1 service ticket(s): " << TInstant::Seconds(now.Seconds()) << "\n" + << "7: File './tmp/retry_settings' does not exist\n" + << "7: Adding dst: got task #1 with 2 dsts\n" + << "7: Adding dst: got task #2 with 2 dsts\n" + << "7: Adding dst: got task #3 with 1 dsts\n" + << "7: Response with service tickets for 4 destination(s) was successfully fetched from https://tvm-api.yandex.net\n" + << "7: Got responses with service tickets with 1 pages for 4 destination(s)\n" + << "3: Failed to get service ticket for dst=456: error_3\n" + << "6: Cache was partly updated with 3 service ticket(s). total: 4\n" + << "6: File './tmp/service_tickets' was successfully written\n" + << "7: Adding dst: task #1: dst=123 got ticket\n" + << "7: Adding dst: task #1: dst=213 got ticket\n" + << "7: Adding dst: task #1: set value\n" + << "7: Adding dst: task #2: dst=213 got ticket\n" + << "4: Adding dst: task #2: dst=456 failed to get ticket: error_3\n" + << "7: Adding dst: task #2: set value\n" + << "3: Adding dst: task #2: exception: planed exc\n" + << "7: Adding dst: task #3: dst=789 got ticket\n" + << "7: Adding dst: task #3: set value\n" + << "3: Adding dst: task #3: exception: unknown error\n", + l->Stream.Str()); + } + + Y_UNIT_TEST(UseCacheOnAddAfterRestart) { + TInstant now = TInstant::Now(); + CleanCache(); + WriteFile("./service_tickets", + R"({"19" : { "ticket" : "3:serv:CBAQ__________9_IgYIKhCUkQY:CX"}})" + "\t100500", + now); + + NTvmApi::TClientSettings s{ + .DiskCacheDir = CACHE_DIR, + .SelfTvmId = 100500, + .Secret = (TStringBuf) "qwerty", + .FetchServiceTicketsForDstsWithAliases = {{"blackbox", 19}}, + .IsIncompleteTicketsSetAnError = false, + }; + + auto l = MakeIntrusive<TLogger>(); + { + TOfflineUpdater client(s, l); + UNIT_ASSERT_VALUES_EQUAL(TClientStatus::Ok, client.GetStatus()); + UNIT_ASSERT(client.GetCachedServiceTickets()->TicketsById.contains(19)); + UNIT_ASSERT(!client.GetCachedServiceTickets()->ErrorsById.contains(19)); + + client.Fail = false; + client.Tickets = { + R"({"123" : { "ticket" : "3:serv:CBAQ__________9_IgQIExB7:V9zPxvEi-VYK2FffYkkT0lF_Ol2XXGzZDH-dVpx0Vo962KDh6n3AtPiCGpYZsLFZkJ5BcujVOUVfONprbm9WqZaPk6qDRuBd-OfYjGRDNUGAP_v5a1bZEBZhaRV8pvceh28E5QsgpOz1SEN5viKgPt1Tz9EmN8abQZiCPj2mqY8"}, + "213" : { "ticket" : "3:serv:CBAQ__________9_IgUIExDVAQ:Ten8XGEC1rupV9GsKvEG9IbN5DazwS3rmpEuFbgk34RaNeLO9g1mAvxKhL76hQnrgi_KjXv_xEtyQ4awBTXeQuhSlC0wkgargsc96AShWBFfHuTM-ftDtn5VdF9d8k1TEsTdJfZsW3_E_wy_3FQhs_rnoIowys6_SAh8YLpqaQk"}})", + }; + NThreading::TFuture<TAddResponse> fut = client.Add({123, 213}); + + client.Worker(); + UNIT_ASSERT_VALUES_EQUAL(TClientStatus::Ok, client.GetStatus()); + + UNIT_ASSERT(client.GetCachedServiceTickets()->TicketsById.contains(19)); + UNIT_ASSERT(client.GetCachedServiceTickets()->TicketsById.contains(213)); + UNIT_ASSERT(client.GetCachedServiceTickets()->TicketsById.contains(123)); + UNIT_ASSERT(!client.GetCachedServiceTickets()->ErrorsById.contains(19)); + UNIT_ASSERT(!client.GetCachedServiceTickets()->ErrorsById.contains(213)); + UNIT_ASSERT(!client.GetCachedServiceTickets()->ErrorsById.contains(123)); + + UNIT_ASSERT(fut.HasValue()); + TAddResponse resp{ + {123, {EDstStatus::Success, ""}}, + {213, {EDstStatus::Success, ""}}, + }; + UNIT_ASSERT_VALUES_EQUAL(resp, fut.GetValue()); + + UNIT_ASSERT(client.Tickets.empty()); + + TDsts dsts{19, 123, 213}; + UNIT_ASSERT_VALUES_EQUAL(dsts, *client.GetDsts()); + } + { + TOfflineUpdater client(s, l); + UNIT_ASSERT_VALUES_EQUAL(TClientStatus::Ok, client.GetStatus()); + UNIT_ASSERT(client.GetCachedServiceTickets()->TicketsById.contains(19)); + UNIT_ASSERT(!client.GetCachedServiceTickets()->ErrorsById.contains(19)); + UNIT_ASSERT(!client.GetCachedServiceTickets()->TicketsById.contains(123)); + UNIT_ASSERT(!client.GetCachedServiceTickets()->TicketsById.contains(213)); + + NThreading::TFuture<TAddResponse> fut = client.Add({123, 213}); + fut.Wait(); + UNIT_ASSERT_VALUES_EQUAL(TClientStatus::Ok, client.GetStatus()); + + UNIT_ASSERT(client.GetCachedServiceTickets()->TicketsById.contains(19)); + UNIT_ASSERT(client.GetCachedServiceTickets()->TicketsById.contains(213)); + UNIT_ASSERT(client.GetCachedServiceTickets()->TicketsById.contains(123)); + UNIT_ASSERT(!client.GetCachedServiceTickets()->ErrorsById.contains(19)); + UNIT_ASSERT(!client.GetCachedServiceTickets()->ErrorsById.contains(213)); + UNIT_ASSERT(!client.GetCachedServiceTickets()->ErrorsById.contains(123)); + + UNIT_ASSERT(fut.HasValue()); + TAddResponse resp{ + {123, {EDstStatus::Success, ""}}, + {213, {EDstStatus::Success, ""}}, + }; + UNIT_ASSERT_VALUES_EQUAL(resp, fut.GetValue()); + + UNIT_ASSERT(client.Tickets.empty()); + + TDsts dsts{19, 123, 213}; + UNIT_ASSERT_VALUES_EQUAL(dsts, *client.GetDsts()); + } + + UNIT_ASSERT_VALUES_EQUAL( + TStringBuilder() + << "6: File './tmp/service_tickets' was successfully read\n" + << "6: Got 1 service ticket(s) from disk\n" + << "6: Cache was updated with 1 service ticket(s): " << TInstant::Seconds(now.Seconds()) << "\n" + << "7: File './tmp/retry_settings' does not exist\n" + << "7: Adding dst: got task #1 with 2 dsts\n" + << "7: Response with service tickets for 2 destination(s) was successfully fetched from https://tvm-api.yandex.net\n" + << "7: Got responses with service tickets with 1 pages for 2 destination(s)\n" + << "6: Cache was partly updated with 2 service ticket(s). total: 3\n" + << "6: File './tmp/service_tickets' was successfully written\n" + << "7: Adding dst: task #1: dst=123 got ticket\n" + << "7: Adding dst: task #1: dst=213 got ticket\n" + << "7: Adding dst: task #1: set value\n" + << "6: File './tmp/service_tickets' was successfully read\n" + << "6: Got 3 service ticket(s) from disk\n" + << "6: Cache was updated with 1 service ticket(s): " << TInstant::Seconds(now.Seconds()) << "\n" + << "7: File './tmp/retry_settings' does not exist\n" + << "6: Cache was updated with 3 service ticket(s): " << TInstant::Seconds(now.Seconds()) << "\n" + << "7: Got ticket from disk cache: dst=123 got ticket\n" + << "7: Got ticket from disk cache: dst=213 got ticket\n", + l->Stream.Str()); + } +} + +template <> +void Out<NTvmAuth::NDynamicClient::TDstResponse>(IOutputStream& out, const NTvmAuth::NDynamicClient::TDstResponse& m) { + out << m.Status << " (" << m.Error << ")"; +} + +template <> +void Out<NTvmAuth::NTvmApi::TClientSettings::TDst>(IOutputStream& out, const NTvmAuth::NTvmApi::TClientSettings::TDst& m) { + out << m.Id; +} diff --git a/library/cpp/tvmauth/client/misc/api/retry_settings.h b/library/cpp/tvmauth/client/misc/api/retry_settings.h new file mode 100644 index 0000000000..607b230811 --- /dev/null +++ b/library/cpp/tvmauth/client/misc/api/retry_settings.h @@ -0,0 +1,33 @@ +#pragma once + +#include <library/cpp/tvmauth/client/misc/exponential_backoff.h> + +namespace NTvmAuth::NTvmApi { + struct TRetrySettings { + TExponentialBackoff::TSettings BackoffSettings = { + TDuration::Seconds(0), + TDuration::Minutes(1), + 2, + 0.5, + }; + TDuration MaxRandomSleepDefault = TDuration::Seconds(5); + TDuration MaxRandomSleepWhenOk = TDuration::Minutes(1); + ui32 RetriesOnStart = 3; + ui32 RetriesInBackground = 2; + TDuration WorkerAwakingPeriod = TDuration::Seconds(10); + ui32 DstsLimit = 300; + TDuration RolesUpdatePeriod = TDuration::Minutes(10); + TDuration RolesWarnPeriod = TDuration::Minutes(20); + + bool operator==(const TRetrySettings& o) const { + return BackoffSettings == o.BackoffSettings && + MaxRandomSleepDefault == o.MaxRandomSleepDefault && + MaxRandomSleepWhenOk == o.MaxRandomSleepWhenOk && + RetriesOnStart == o.RetriesOnStart && + WorkerAwakingPeriod == o.WorkerAwakingPeriod && + DstsLimit == o.DstsLimit && + RolesUpdatePeriod == o.RolesUpdatePeriod && + RolesWarnPeriod == o.RolesWarnPeriod; + } + }; +} diff --git a/library/cpp/tvmauth/client/misc/api/roles_fetcher.cpp b/library/cpp/tvmauth/client/misc/api/roles_fetcher.cpp new file mode 100644 index 0000000000..8f4b359e8c --- /dev/null +++ b/library/cpp/tvmauth/client/misc/api/roles_fetcher.cpp @@ -0,0 +1,164 @@ +#include "roles_fetcher.h" + +#include <library/cpp/tvmauth/client/misc/disk_cache.h> +#include <library/cpp/tvmauth/client/misc/roles/decoder.h> +#include <library/cpp/tvmauth/client/misc/roles/parser.h> + +#include <library/cpp/http/misc/httpcodes.h> +#include <library/cpp/string_utils/quote/quote.h> + +#include <util/string/builder.h> +#include <util/string/join.h> + +namespace NTvmAuth::NTvmApi { + static TString CreatePath(const TString& dir, const TString& file) { + return dir.EndsWith("/") + ? dir + file + : dir + "/" + file; + } + + TRolesFetcher::TRolesFetcher(const TRolesFetcherSettings& settings, TLoggerPtr logger) + : Settings_(settings) + , Logger_(logger) + , CacheFilePath_(CreatePath(Settings_.CacheDir, "roles")) + { + Client_ = std::make_unique<TKeepAliveHttpClient>( + Settings_.TiroleHost, + Settings_.TirolePort, + Settings_.Timeout, + Settings_.Timeout); + } + + TInstant TRolesFetcher::ReadFromDisk() { + TDiskReader dr(CacheFilePath_, Logger_.Get()); + if (!dr.Read()) { + return {}; + } + + std::pair<TString, TString> data = ParseDiskFormat(dr.Data()); + if (data.second != Settings_.IdmSystemSlug) { + Logger_->Warning( + TStringBuilder() << "Roles in disk cache are for another slug (" << data.second + << "). Self=" << Settings_.IdmSystemSlug); + return {}; + } + + CurrentRoles_.Set(NRoles::TParser::Parse(std::make_shared<TString>(std::move(data.first)))); + Logger_->Debug( + TStringBuilder() << "Succeed to read roles with revision " + << CurrentRoles_.Get()->GetMeta().Revision + << " from " << CacheFilePath_); + + return dr.Time(); + } + + bool TRolesFetcher::AreRolesOk() const { + return bool(GetCurrentRoles()); + } + + bool TRolesFetcher::IsTimeToUpdate(const TRetrySettings& settings, TDuration sinceUpdate) { + return settings.RolesUpdatePeriod < sinceUpdate; + } + + bool TRolesFetcher::ShouldWarn(const TRetrySettings& settings, TDuration sinceUpdate) { + return settings.RolesWarnPeriod < sinceUpdate; + } + + NUtils::TFetchResult TRolesFetcher::FetchActualRoles(const TString& serviceTicket) { + TStringStream out; + THttpHeaders outHeaders; + + TRequest req = CreateTiroleRequest(serviceTicket); + TKeepAliveHttpClient::THttpCode code = Client_->DoGet( + req.Url, + &out, + req.Headers, + &outHeaders); + + const THttpInputHeader* reqId = outHeaders.FindHeader("X-Request-Id"); + + Logger_->Debug( + TStringBuilder() << "Succeed to perform request for roles to " << Settings_.TiroleHost + << " (request_id=" << (reqId ? reqId->Value() : "") + << "). code=" << code); + + return {code, std::move(outHeaders), "/v1/get_actual_roles", out.Str(), {}}; + } + + void TRolesFetcher::Update(NUtils::TFetchResult&& fetchResult, TInstant now) { + if (fetchResult.Code == HTTP_NOT_MODIFIED) { + Y_ENSURE(CurrentRoles_.Get(), + "tirole did not return any roles because current roles are actual," + " but there are no roles in memory - this should never happen"); + return; + } + + Y_ENSURE(fetchResult.Code == HTTP_OK, + "Unexpected code from tirole: " << fetchResult.Code << ". " << fetchResult.Response); + + const THttpInputHeader* codec = fetchResult.Headers.FindHeader("X-Tirole-Compression"); + const TStringBuf codecBuf = codec ? codec->Value() : ""; + + NRoles::TRawPtr blob; + try { + blob = std::make_shared<TString>(NRoles::TDecoder::Decode( + codecBuf, + std::move(fetchResult.Response))); + } catch (const std::exception& e) { + throw yexception() << "Failed to decode blob with codec '" << codecBuf + << "': " << e.what(); + } + + CurrentRoles_.Set(NRoles::TParser::Parse(blob)); + + Logger_->Debug( + TStringBuilder() << "Succeed to update roles with revision " + << CurrentRoles_.Get()->GetMeta().Revision); + + TDiskWriter dw(CacheFilePath_, Logger_.Get()); + dw.Write(PrepareDiskFormat(*blob, Settings_.IdmSystemSlug), now); + } + + NTvmAuth::NRoles::TRolesPtr TRolesFetcher::GetCurrentRoles() const { + return CurrentRoles_.Get(); + } + + void TRolesFetcher::ResetConnection() { + Client_->ResetConnection(); + } + + static const char DELIMETER = '\t'; + + std::pair<TString, TString> TRolesFetcher::ParseDiskFormat(TStringBuf filebody) { + TStringBuf slug = filebody.RNextTok(DELIMETER); + return {TString(filebody), CGIUnescapeRet(slug)}; + } + + TString TRolesFetcher::PrepareDiskFormat(TStringBuf roles, TStringBuf slug) { + TStringStream res; + res.Reserve(roles.size() + 1 + slug.size()); + res << roles << DELIMETER << CGIEscapeRet(slug); + return res.Str(); + } + + TRolesFetcher::TRequest TRolesFetcher::CreateTiroleRequest(const TString& serviceTicket) const { + TRolesFetcher::TRequest res; + + TStringStream url; + url.Reserve(512); + url << "/v1/get_actual_roles?"; + url << "system_slug=" << CGIEscapeRet(Settings_.IdmSystemSlug) << "&"; + Settings_.ProcInfo.AddToRequest(url); + res.Url = std::move(url.Str()); + + res.Headers.reserve(2); + res.Headers.emplace(XYaServiceTicket_, serviceTicket); + + NRoles::TRolesPtr roles = CurrentRoles_.Get(); + if (roles) { + res.Headers.emplace(IfNoneMatch_, Join("", "\"", roles->GetMeta().Revision, "\"")); + } + + return res; + } +} diff --git a/library/cpp/tvmauth/client/misc/api/roles_fetcher.h b/library/cpp/tvmauth/client/misc/api/roles_fetcher.h new file mode 100644 index 0000000000..63691223b5 --- /dev/null +++ b/library/cpp/tvmauth/client/misc/api/roles_fetcher.h @@ -0,0 +1,63 @@ +#pragma once + +#include "retry_settings.h" + +#include <library/cpp/tvmauth/client/misc/fetch_result.h> +#include <library/cpp/tvmauth/client/misc/proc_info.h> +#include <library/cpp/tvmauth/client/misc/utils.h> +#include <library/cpp/tvmauth/client/misc/roles/roles.h> + +#include <library/cpp/tvmauth/client/logger.h> + +#include <library/cpp/http/simple/http_client.h> + +namespace NTvmAuth::NTvmApi { + struct TRolesFetcherSettings { + TString TiroleHost; + ui16 TirolePort = 0; + TString CacheDir; + NUtils::TProcInfo ProcInfo; + TTvmId SelfTvmId = 0; + TString IdmSystemSlug; + TDuration Timeout = TDuration::Seconds(30); + }; + + class TRolesFetcher { + public: + TRolesFetcher(const TRolesFetcherSettings& settings, TLoggerPtr logger); + + TInstant ReadFromDisk(); + + bool AreRolesOk() const; + static bool IsTimeToUpdate(const TRetrySettings& settings, TDuration sinceUpdate); + static bool ShouldWarn(const TRetrySettings& settings, TDuration sinceUpdate); + + NUtils::TFetchResult FetchActualRoles(const TString& serviceTicket); + void Update(NUtils::TFetchResult&& fetchResult, TInstant now = TInstant::Now()); + + NTvmAuth::NRoles::TRolesPtr GetCurrentRoles() const; + + void ResetConnection(); + + public: + static std::pair<TString, TString> ParseDiskFormat(TStringBuf filebody); + static TString PrepareDiskFormat(TStringBuf roles, TStringBuf slug); + + struct TRequest { + TString Url; + TKeepAliveHttpClient::THeaders Headers; + }; + TRequest CreateTiroleRequest(const TString& serviceTicket) const; + + private: + const TRolesFetcherSettings Settings_; + const TLoggerPtr Logger_; + const TString CacheFilePath_; + const TString XYaServiceTicket_ = "X-Ya-Service-Ticket"; + const TString IfNoneMatch_ = "If-None-Match"; + + NUtils::TProtectedValue<NTvmAuth::NRoles::TRolesPtr> CurrentRoles_; + + std::unique_ptr<TKeepAliveHttpClient> Client_; + }; +} diff --git a/library/cpp/tvmauth/client/misc/api/settings.cpp b/library/cpp/tvmauth/client/misc/api/settings.cpp new file mode 100644 index 0000000000..eddad21869 --- /dev/null +++ b/library/cpp/tvmauth/client/misc/api/settings.cpp @@ -0,0 +1,89 @@ +#include "settings.h" + +#include <util/datetime/base.h> +#include <util/stream/file.h> +#include <util/system/fs.h> + +#include <set> + +namespace NTvmAuth::NTvmApi { + void TClientSettings::CheckPermissions(const TString& dir) { + const TString name = dir + "/check.tmp"; + + try { + NFs::EnsureExists(dir); + + TFile file(name, CreateAlways | RdWr); + + NFs::Remove(name); + } catch (const std::exception& e) { + NFs::Remove(name); + ythrow TPermissionDenied() << "Permission denied to disk cache directory: " << e.what(); + } + } + + void TClientSettings::CheckValid() const { + if (DiskCacheDir) { + CheckPermissions(DiskCacheDir); + } + + if (TStringBuf(Secret)) { + Y_ENSURE_EX(NeedServiceTicketsFetching(), + TBrokenTvmClientSettings() << "Secret is present but destinations list is empty. It makes no sense"); + } + if (NeedServiceTicketsFetching()) { + Y_ENSURE_EX(SelfTvmId != 0, + TBrokenTvmClientSettings() << "SelfTvmId cannot be 0 if fetching of Service Tickets required"); + Y_ENSURE_EX((TStringBuf)Secret, + TBrokenTvmClientSettings() << "Secret is required for fetching of Service Tickets"); + } + + if (CheckServiceTickets) { + Y_ENSURE_EX(SelfTvmId != 0, + TBrokenTvmClientSettings() << "SelfTvmId cannot be 0 if checking of Service Tickets required"); + } + + if (FetchRolesForIdmSystemSlug) { + Y_ENSURE_EX(DiskCacheDir, + TBrokenTvmClientSettings() << "Disk cache must be enabled to use roles: " + "they can be heavy"); + } + + bool needSmth = NeedServiceTicketsFetching() || + CheckServiceTickets || + CheckUserTicketsWithBbEnv; + Y_ENSURE_EX(needSmth, TBrokenTvmClientSettings() << "Invalid settings: nothing to do"); + + // Useless now: keep it here to avoid forgetting check from TDst. TODO: PASSP-35377 + for (const auto& dst : FetchServiceTicketsForDsts) { + Y_ENSURE_EX(dst.Id != 0, TBrokenTvmClientSettings() << "TvmId cannot be 0"); + } + // TODO: check only FetchServiceTicketsForDsts_ + // Python binding checks settings before normalization + for (const auto& [alias, dst] : FetchServiceTicketsForDstsWithAliases) { + Y_ENSURE_EX(dst.Id != 0, TBrokenTvmClientSettings() << "TvmId cannot be 0"); + } + Y_ENSURE_EX(TiroleTvmId != 0, TBrokenTvmClientSettings() << "TiroleTvmId cannot be 0"); + } + + TClientSettings TClientSettings::CloneNormalized() const { + TClientSettings res = *this; + + std::set<TTvmId> allDsts; + for (const auto& tvmid : res.FetchServiceTicketsForDsts) { + allDsts.insert(tvmid.Id); + } + for (const auto& [alias, tvmid] : res.FetchServiceTicketsForDstsWithAliases) { + allDsts.insert(tvmid.Id); + } + if (FetchRolesForIdmSystemSlug) { + allDsts.insert(res.TiroleTvmId); + } + + res.FetchServiceTicketsForDsts = {allDsts.begin(), allDsts.end()}; + + res.CheckValid(); + + return res; + } +} diff --git a/library/cpp/tvmauth/client/misc/api/settings.h b/library/cpp/tvmauth/client/misc/api/settings.h new file mode 100644 index 0000000000..4f901dda57 --- /dev/null +++ b/library/cpp/tvmauth/client/misc/api/settings.h @@ -0,0 +1,236 @@ +#pragma once + +#include <library/cpp/tvmauth/client/misc/settings.h> + +#include <library/cpp/tvmauth/client/exception.h> + +#include <library/cpp/tvmauth/checked_user_ticket.h> +#include <library/cpp/tvmauth/type.h> + +#include <library/cpp/string_utils/secret_string/secret_string.h> + +#include <util/datetime/base.h> +#include <util/generic/hash.h> +#include <util/generic/maybe.h> + +namespace NTvmAuth::NTvmApi { + /** + * Settings for TVM client. Uses https://tvm-api.yandex.net to get state. + * At least one of them is required: + * FetchServiceTicketsForDsts_/FetchServiceTicketsForDstsWithAliases_ + * CheckServiceTickets_ + * CheckUserTicketsWithBbEnv_ + */ + class TClientSettings: public NTvmAuth::TClientSettings { + public: + class TDst; + + /** + * Alias is an internal name for destinations within your code. + * You can associate a name with an tvm_id once in your code and use the name as an alias for + * tvm_id to each calling point. Useful for several environments: prod/test/etc. + * @example: + * // init + * static const TString MY_BACKEND = "my backend"; + * TDstMap map = {{MY_BACKEND, TDst(config.get("my_back_tvm_id"))}}; + * ... + * // per request + * TString t = tvmClient.GetServiceTicket(MY_BACKEND); + */ + using TDstMap = THashMap<TAlias, TDst>; + using TDstVector = TVector<TDst>; + + public: + /*! + * NOTE: Please use this option: it provides the best reliability + * NOTE: Client requires read/write permissions + * WARNING: The same directory can be used only: + * - for TVM clients with the same settings + * OR + * - for new client replacing previous - with another config. + * System user must be the same for processes with these clients inside. + * Implementation doesn't provide other scenarios. + */ + TString DiskCacheDir; + + // Required for Service Ticket fetching or checking + TTvmId SelfTvmId = 0; + + // Options for Service Tickets fetching + NSecretString::TSecretString Secret; + /*! + * Client will process both attrs: + * FetchServiceTicketsForDsts_, FetchServiceTicketsForDstsWithAliases_ + * WARNING: It is not way to provide authorization for incoming ServiceTickets! + * It is way only to send your ServiceTickets to your backend! + */ + TDstVector FetchServiceTicketsForDsts; + TDstMap FetchServiceTicketsForDstsWithAliases; + bool IsIncompleteTicketsSetAnError = true; + + // Options for Service Tickets checking + bool CheckServiceTickets = false; + + // Options for User Tickets checking + TMaybe<EBlackboxEnv> CheckUserTicketsWithBbEnv; + + // Options for roles fetching + TString FetchRolesForIdmSystemSlug; + /*! + * By default client checks src from ServiceTicket or default uid from UserTicket - + * to prevent you from forgetting to check it yourself. + * It does binary checks only: + * ticket gets status NoRoles, if there is no role for src or default uid. + * You need to check roles on your own if you have a non-binary role system or + * you have disabled ShouldCheckSrc/ShouldCheckDefaultUid + * + * You may need to disable this check in the following cases: + * - You use GetRoles() to provide verbose message (with revision). + * Double check may be inconsistent: + * binary check inside client uses revision of roles X - i.e. src 100500 has no role, + * exact check in your code uses revision of roles Y - i.e. src 100500 has some roles. + */ + bool ShouldCheckSrc = true; + bool ShouldCheckDefaultUid = true; + /*! + * By default client checks dst from ServiceTicket. If this check is switched off + * incorrect dst does not result in error of checked ticket status + * DANGEROUS: This case you must check dst manualy using @link TCheckedServiceTicket::GetDst() + */ + bool ShouldCheckDst = true; + + // Options for tests + TString TvmHost = "https://tvm-api.yandex.net"; + ui16 TvmPort = 443; + TString TiroleHost = "https://tirole-api.yandex.net"; + TDuration TvmSocketTimeout = TDuration::Seconds(5); + TDuration TvmConnectTimeout = TDuration::Seconds(30); + ui16 TirolePort = 443; + TTvmId TiroleTvmId = TIROLE_TVMID; + + // for debug purposes + TString LibVersionPrefix; + + void CheckValid() const; + TClientSettings CloneNormalized() const; + + static inline const TTvmId TIROLE_TVMID = 2028120; + static inline const TTvmId TIROLE_TVMID_TEST = 2026536; + + // DEPRECATED API + // TODO: get rid of it: PASSP-35377 + public: + // Deprecated: set attributes directly + void SetSelfTvmId(TTvmId selfTvmId) { + SelfTvmId = selfTvmId; + } + + // Deprecated: set attributes directly + void EnableServiceTicketChecking() { + CheckServiceTickets = true; + } + + // Deprecated: set attributes directly + void EnableUserTicketChecking(EBlackboxEnv env) { + CheckUserTicketsWithBbEnv = env; + } + + // Deprecated: set attributes directly + void SetTvmHostPort(const TString& host, ui16 port) { + TvmHost = host; + TvmPort = port; + } + + // Deprecated: set attributes directly + void SetTiroleHostPort(const TString& host, ui16 port) { + TiroleHost = host; + TirolePort = port; + } + + // Deprecated: set attributes directly + void EnableRolesFetching(const TString& systemSlug, TTvmId tiroleTvmId = TIROLE_TVMID) { + TiroleTvmId = tiroleTvmId; + FetchRolesForIdmSystemSlug = systemSlug; + } + + // Deprecated: set attributes directly + void DoNotCheckSrcByDefault() { + ShouldCheckSrc = false; + } + + // Deprecated: set attributes directly + void DoNotCheckDefaultUidByDefault() { + ShouldCheckDefaultUid = false; + } + + // Deprecated: set attributes directly + void SetDiskCacheDir(const TString& dir) { + DiskCacheDir = dir; + } + + // Deprecated: set attributes directly + void EnableServiceTicketsFetchOptions(const TStringBuf selfSecret, + TDstMap&& dsts, + const bool considerIncompleteTicketsSetAsError = true) { + IsIncompleteTicketsSetAnError = considerIncompleteTicketsSetAsError; + Secret = selfSecret; + + FetchServiceTicketsForDsts = TDstVector{}; + FetchServiceTicketsForDsts.reserve(dsts.size()); + for (const auto& pair : dsts) { + FetchServiceTicketsForDsts.push_back(pair.second); + } + + FetchServiceTicketsForDstsWithAliases = std::move(dsts); + } + + // Deprecated: set attributes directly + void EnableServiceTicketsFetchOptions(const TStringBuf selfSecret, + TDstVector&& dsts, + const bool considerIncompleteTicketsSetAsError = true) { + IsIncompleteTicketsSetAnError = considerIncompleteTicketsSetAsError; + Secret = selfSecret; + FetchServiceTicketsForDsts = std::move(dsts); + } + + public: + bool IsServiceTicketFetchingRequired() const { + return bool(Secret.Value()); + } + + bool NeedServiceTicketsFetching() const { + return !FetchServiceTicketsForDsts.empty() || + !FetchServiceTicketsForDstsWithAliases.empty() || + FetchRolesForIdmSystemSlug; + } + + // TODO: get rid of TDst: PASSP-35377 + class TDst { + public: + TDst(TTvmId id) + : Id(id) + { + Y_ENSURE_EX(id != 0, TBrokenTvmClientSettings() << "TvmId cannot be 0"); + } + + TTvmId Id; + + bool operator==(const TDst& o) const { + return Id == o.Id; + } + + bool operator<(const TDst& o) const { + return Id < o.Id; + } + + public: // for python binding + TDst() + : Id(0) + { + } + }; + + public: + static void CheckPermissions(const TString& dir); + }; +} diff --git a/library/cpp/tvmauth/client/misc/api/threaded_updater.cpp b/library/cpp/tvmauth/client/misc/api/threaded_updater.cpp new file mode 100644 index 0000000000..c4a6415be8 --- /dev/null +++ b/library/cpp/tvmauth/client/misc/api/threaded_updater.cpp @@ -0,0 +1,1049 @@ +#include "threaded_updater.h" + +#include <library/cpp/tvmauth/client/misc/disk_cache.h> +#include <library/cpp/tvmauth/client/misc/utils.h> +#include <library/cpp/tvmauth/client/misc/retry_settings/v1/settings.pb.h> + +#include <library/cpp/tvmauth/client/logger.h> + +#include <library/cpp/json/json_reader.h> + +#include <util/stream/str.h> +#include <util/string/builder.h> +#include <util/string/cast.h> +#include <util/system/thread.h> + +namespace NTvmAuth::NTvmApi { + static TString CreatePublicKeysUrl(const TClientSettings& settings, + const NUtils::TProcInfo& procInfo) { + TStringStream s; + s << "/2/keys"; + s << "?"; + procInfo.AddToRequest(s); + + s << "&get_retry_settings=yes"; + + if (settings.SelfTvmId != 0) { + s << "&src=" << settings.SelfTvmId; + } + + if (settings.CheckUserTicketsWithBbEnv) { + s << "&env=" << static_cast<int>(*settings.CheckUserTicketsWithBbEnv); + } + + return s.Str(); + } + + TAsyncUpdaterPtr TThreadedUpdater::Create(const TClientSettings& settings, TLoggerPtr logger) { + Y_ENSURE_EX(logger, TNonRetriableException() << "Logger is required"); + THolder<TThreadedUpdater> p(new TThreadedUpdater(settings, std::move(logger))); + p->Init(); + p->StartWorker(); + return p.Release(); + } + + TThreadedUpdater::~TThreadedUpdater() { + ExpBackoff_.SetEnabled(false); + ExpBackoff_.Interrupt(); + StopWorker(); // Required here to avoid using of deleted members + } + + TClientStatus TThreadedUpdater::GetStatus() const { + const TClientStatus::ECode state = GetState(); + return TClientStatus(state, GetLastError(state == TClientStatus::Ok || state == TClientStatus::IncompleteTicketsSet)); + } + + NRoles::TRolesPtr TThreadedUpdater::GetRoles() const { + Y_ENSURE_EX(RolesFetcher_, + TBrokenTvmClientSettings() << "Roles were not configured in settings"); + return RolesFetcher_->GetCurrentRoles(); + } + + TClientStatus::ECode TThreadedUpdater::GetState() const { + const TInstant now = TInstant::Now(); + + if (Settings_.IsServiceTicketFetchingRequired()) { + if (AreServiceTicketsInvalid(now)) { + return TClientStatus::Error; + } + auto tickets = GetCachedServiceTickets(); + if (!tickets) { + return TClientStatus::Error; + } + if (tickets->TicketsById.size() < GetDsts()->size()) { + if (Settings_.IsIncompleteTicketsSetAnError) { + return TClientStatus::Error; + } else { + return TClientStatus::IncompleteTicketsSet; + } + } + } + if ((Settings_.CheckServiceTickets || Settings_.CheckUserTicketsWithBbEnv) && ArePublicKeysInvalid(now)) { + return TClientStatus::Error; + } + + const TDuration sincePublicKeysUpdate = now - GetUpdateTimeOfPublicKeys(); + const TDuration sinceServiceTicketsUpdate = now - GetUpdateTimeOfServiceTickets(); + const TDuration sinceRolesUpdate = now - GetUpdateTimeOfRoles(); + + if (Settings_.IsServiceTicketFetchingRequired() && sinceServiceTicketsUpdate > ServiceTicketsDurations_.Expiring) { + return TClientStatus::Warning; + } + if ((Settings_.CheckServiceTickets || Settings_.CheckUserTicketsWithBbEnv) && + sincePublicKeysUpdate > PublicKeysDurations_.Expiring) + { + return TClientStatus::Warning; + } + if (RolesFetcher_ && TRolesFetcher::ShouldWarn(RetrySettings_, sinceRolesUpdate)) { + return TClientStatus::Warning; + } + + return TClientStatus::Ok; + } + + TThreadedUpdater::TThreadedUpdater(const TClientSettings& settings, TLoggerPtr logger) + : TThreadedUpdaterBase( + TRetrySettings{}.WorkerAwakingPeriod, + std::move(logger), + settings.TvmHost, + settings.TvmPort, + settings.TvmSocketTimeout, + settings.TvmConnectTimeout) + , ExpBackoff_(RetrySettings_.BackoffSettings) + , ServiceTicketBatchUpdateMutex_(std::make_unique<std::mutex>()) + , Settings_(settings.CloneNormalized()) + , ProcInfo_(NUtils::TProcInfo::Create(Settings_.LibVersionPrefix)) + , PublicKeysUrl_(CreatePublicKeysUrl(Settings_, ProcInfo_)) + , DstAliases_(MakeAliasMap(Settings_)) + , Headers_({{"Content-Type", "application/x-www-form-urlencoded"}}) + , Random_(TInstant::Now().MicroSeconds()) + { + if (Settings_.IsServiceTicketFetchingRequired()) { + SigningContext_ = TServiceContext::SigningFactory(Settings_.Secret); + } + + if (Settings_.IsServiceTicketFetchingRequired()) { + Destinations_ = std::make_shared<const TDstSet>(Settings_.FetchServiceTicketsForDsts.begin(), Settings_.FetchServiceTicketsForDsts.end()); + } + + PublicKeysDurations_.RefreshPeriod = TDuration::Days(1); + ServiceTicketsDurations_.RefreshPeriod = TDuration::Hours(1); + + if (Settings_.CheckUserTicketsWithBbEnv) { + SetBbEnv(*Settings_.CheckUserTicketsWithBbEnv); + } + + if (Settings_.FetchRolesForIdmSystemSlug) { + RolesFetcher_ = std::make_unique<TRolesFetcher>( + TRolesFetcherSettings{ + Settings_.TiroleHost, + Settings_.TirolePort, + Settings_.DiskCacheDir, + ProcInfo_, + Settings_.SelfTvmId, + Settings_.FetchRolesForIdmSystemSlug, + }, + Logger_); + } + + if (Settings_.DiskCacheDir) { + TString path = Settings_.DiskCacheDir; + if (path.back() != '/') { + path.push_back('/'); + } + + if (Settings_.IsServiceTicketFetchingRequired()) { + ServiceTicketsFilepath_ = path; + ServiceTicketsFilepath_.append("service_tickets"); + } + + if (Settings_.CheckServiceTickets || Settings_.CheckUserTicketsWithBbEnv) { + PublicKeysFilepath_ = path; + PublicKeysFilepath_.append("public_keys"); + } + + RetrySettingsFilepath_ = path + "retry_settings"; + } else { + LogInfo("Disk cache disabled. Please set disk cache directory in settings for best reliability"); + } + } + + void TThreadedUpdater::Init() { + ReadStateFromDisk(); + ClearErrors(); + ExpBackoff_.SetEnabled(false); + + // First of all try to get tickets: there are a lot of reasons to fail this request. + // As far as disk cache usually disabled, client will not fetch keys before fail on every ctor call. + UpdateServiceTickets(); + if (!AreServicesTicketsOk()) { + ThrowLastError(); + } + + UpdatePublicKeys(); + if (!IsServiceContextOk() || !IsUserContextOk()) { + ThrowLastError(); + } + + UpdateRoles(); + if (RolesFetcher_ && !RolesFetcher_->AreRolesOk()) { + ThrowLastError(); + } + + Inited_ = true; + ExpBackoff_.SetEnabled(true); + } + + void TThreadedUpdater::UpdateServiceTickets() { + if (!Settings_.IsServiceTicketFetchingRequired()) { + return; + } + + TInstant stut = GetUpdateTimeOfServiceTickets(); + try { + if (IsTimeToUpdateServiceTickets(stut)) { + UpdateAllServiceTickets(); + NeedFetchMissingServiceTickets_ = false; + } else if (NeedFetchMissingServiceTickets_) { + TDstSetPtr dsts = GetDsts(); + if (GetCachedServiceTickets()->TicketsById.size() < dsts->size()) { + UpdateMissingServiceTickets(*dsts); + NeedFetchMissingServiceTickets_ = false; + } + } + if (AreServicesTicketsOk()) { + ClearError(EScope::ServiceTickets); + } + } catch (const std::exception& e) { + ProcessError(EType::Retriable, EScope::ServiceTickets, e.what()); + LogWarning(TStringBuilder() << "Failed to update service tickets: " << e.what()); + if (TInstant::Now() - stut > ServiceTicketsDurations_.Expiring) { + LogError("Service tickets have not been refreshed for too long period"); + } + } + } + + void TThreadedUpdater::UpdateAllServiceTickets() { + TDstSetPtr dsts = GetDsts(); + THttpResult st = GetServiceTicketsFromHttp(*dsts, RetrySettings_.DstsLimit); + + std::unique_lock lock(*ServiceTicketBatchUpdateMutex_); + + auto oldCache = GetCachedServiceTickets(); + if (oldCache) { + if (dsts->size() < GetDsts()->size()) { + for (const auto& pair : oldCache->TicketsById) { + st.TicketsWithErrors.Tickets.insert(pair); + } + } + + for (const auto& pair : oldCache->ErrorsById) { + st.TicketsWithErrors.Errors.insert(pair); + } + } + + UpdateServiceTicketsCache(std::move(st.TicketsWithErrors), TInstant::Now()); + + lock.unlock(); + + if (ServiceTicketsFilepath_) { + DiskCacheServiceTickets_ = CreateJsonArray(st.Responses); + TDiskWriter w(ServiceTicketsFilepath_, Logger_.Get()); + w.Write(PrepareTicketsForDisk(DiskCacheServiceTickets_, Settings_.SelfTvmId)); + } + } + + TServiceTicketsPtr TThreadedUpdater::UpdateMissingServiceTickets(const TDstSet& required) { + TServiceTicketsPtr cache = GetCachedServiceTickets(); + TClientSettings::TDstVector missingDsts = FindMissingDsts(cache, required); + + if (missingDsts.empty()) { + return cache; + } + + THttpResult st = GetServiceTicketsFromHttp(missingDsts, RetrySettings_.DstsLimit); + + std::unique_lock lock(*ServiceTicketBatchUpdateMutex_); + + cache = GetCachedServiceTickets(); + size_t gotTickets = st.TicketsWithErrors.Tickets.size(); + + TDstSetPtr oldDsts = GetDsts(); + std::shared_ptr<TDstSet> newDsts = std::make_shared<TDstSet>(oldDsts->begin(), oldDsts->end()); + + for (const auto& pair : cache->TicketsById) { + st.TicketsWithErrors.Tickets.insert(pair); + } + for (const auto& pair : cache->ErrorsById) { + st.TicketsWithErrors.Errors.insert(pair); + } + for (const auto& pair : st.TicketsWithErrors.Tickets) { + st.TicketsWithErrors.Errors.erase(pair.first); + newDsts->insert(pair.first); + } + + TServiceTicketsPtr c = UpdateServiceTicketsCachePartly( + std::move(st.TicketsWithErrors), + gotTickets); + + SetDsts(std::move(newDsts)); + + lock.unlock(); + + if (!c) { + LogWarning("UpdateMissingServiceTickets: new cache is NULL. BUG?"); + c = cache; + } + + if (!ServiceTicketsFilepath_) { + return c; + } + + DiskCacheServiceTickets_ = AppendToJsonArray(DiskCacheServiceTickets_, st.Responses); + + TDiskWriter w(ServiceTicketsFilepath_, Logger_.Get()); + w.Write(PrepareTicketsForDisk(DiskCacheServiceTickets_, Settings_.SelfTvmId)); + + return c; + } + + void TThreadedUpdater::UpdatePublicKeys() { + if (!Settings_.CheckServiceTickets && !Settings_.CheckUserTicketsWithBbEnv) { + return; + } + + TInstant pkut = GetUpdateTimeOfPublicKeys(); + if (!IsTimeToUpdatePublicKeys(pkut)) { + return; + } + + try { + TString publicKeys = GetPublicKeysFromHttp(); + + UpdatePublicKeysCache(publicKeys, TInstant::Now()); + if (PublicKeysFilepath_) { + TDiskWriter w(PublicKeysFilepath_, Logger_.Get()); + w.Write(publicKeys); + } + if (IsServiceContextOk() && IsUserContextOk()) { + ClearError(EScope::PublicKeys); + } + } catch (const std::exception& e) { + ProcessError(EType::Retriable, EScope::PublicKeys, e.what()); + LogWarning(TStringBuilder() << "Failed to update public keys: " << e.what()); + if (TInstant::Now() - pkut > PublicKeysDurations_.Expiring) { + LogError("Public keys have not been refreshed for too long period"); + } + } + } + + void TThreadedUpdater::UpdateRoles() { + if (!RolesFetcher_) { + return; + } + + TInstant rut = GetUpdateTimeOfRoles(); + if (!TRolesFetcher::IsTimeToUpdate(RetrySettings_, TInstant::Now() - rut)) { + return; + } + + struct TCloser { + TRolesFetcher* Fetcher; + ~TCloser() { + Fetcher->ResetConnection(); + } + } closer{RolesFetcher_.get()}; + + try { + TServiceTicketsPtr st = GetCachedServiceTickets(); + Y_ENSURE(st, "No one service ticket in memory: how it possible?"); + auto it = st->TicketsById.find(Settings_.TiroleTvmId); + Y_ENSURE(it != st->TicketsById.end(), + "Missing tvmid for tirole in cache: " << Settings_.TiroleTvmId); + + RolesFetcher_->Update( + FetchWithRetries( + [&]() { return RolesFetcher_->FetchActualRoles(it->second); }, + EScope::Roles)); + SetUpdateTimeOfRoles(TInstant::Now()); + + if (RolesFetcher_->AreRolesOk()) { + ClearError(EScope::Roles); + } + } catch (const std::exception& e) { + ProcessError(EType::Retriable, EScope::Roles, e.what()); + LogWarning(TStringBuilder() << "Failed to update roles: " << e.what()); + if (TRolesFetcher::ShouldWarn(RetrySettings_, TInstant::Now() - rut)) { + LogError("Roles have not been refreshed for too long period"); + } + } + } + + TServiceTicketsPtr TThreadedUpdater::UpdateServiceTicketsCachePartly( + TAsyncUpdaterBase::TPairTicketsErrors&& tickets, + size_t got) { + size_t count = tickets.Tickets.size(); + TServiceTicketsPtr c = MakeIntrusiveConst<TServiceTickets>(std::move(tickets.Tickets), + std::move(tickets.Errors), + DstAliases_); + SetServiceTickets(c); + LogInfo(TStringBuilder() + << "Cache was partly updated with " << got + << " service ticket(s). total: " << count); + + return c; + } + + void TThreadedUpdater::UpdateServiceTicketsCache(TPairTicketsErrors&& tickets, TInstant time) { + size_t count = tickets.Tickets.size(); + SetServiceTickets(MakeIntrusiveConst<TServiceTickets>(std::move(tickets.Tickets), + std::move(tickets.Errors), + DstAliases_)); + + SetUpdateTimeOfServiceTickets(time); + + if (count > 0) { + LogInfo(TStringBuilder() << "Cache was updated with " << count << " service ticket(s): " << time); + } + } + + void TThreadedUpdater::UpdatePublicKeysCache(const TString& publicKeys, TInstant time) { + if (publicKeys.empty()) { + return; + } + + if (Settings_.CheckServiceTickets) { + SetServiceContext(MakeIntrusiveConst<TServiceContext>( + TServiceContext::CheckingFactory(Settings_.SelfTvmId, + publicKeys))); + } + + if (Settings_.CheckUserTicketsWithBbEnv) { + SetUserContext(publicKeys); + } + + SetUpdateTimeOfPublicKeys(time); + + LogInfo(TStringBuilder() << "Cache was updated with public keys: " << time); + } + + void TThreadedUpdater::ReadStateFromDisk() { + try { + TServiceTicketsFromDisk st; + std::tie(st, StartUpCache_) = ReadServiceTicketsFromDisk(); + UpdateServiceTicketsCache(std::move(st.TicketsWithErrors), st.BornDate); + DiskCacheServiceTickets_ = st.FileBody; + } catch (const std::exception& e) { + LogWarning(TStringBuilder() << "Failed to read service tickets from disk: " << e.what()); + } + + try { + std::pair<TString, TInstant> pk = ReadPublicKeysFromDisk(); + UpdatePublicKeysCache(pk.first, pk.second); + } catch (const std::exception& e) { + LogWarning(TStringBuilder() << "Failed to read public keys from disk: " << e.what()); + } + + try { + TString rs = ReadRetrySettingsFromDisk(); + UpdateRetrySettings(rs); + } catch (const std::exception& e) { + LogWarning(TStringBuilder() << "Failed to read retry settings from disk: " << e.what()); + } + + try { + if (RolesFetcher_) { + SetUpdateTimeOfRoles(RolesFetcher_->ReadFromDisk()); + } + } catch (const std::exception& e) { + LogWarning(TStringBuilder() << "Failed to read roles from disk: " << e.what()); + } + } + + std::pair<TThreadedUpdater::TServiceTicketsFromDisk, TThreadedUpdater::TServiceTicketsFromDisk> TThreadedUpdater::ReadServiceTicketsFromDisk() const { + if (!ServiceTicketsFilepath_) { + return {}; + } + + TDiskReader r(ServiceTicketsFilepath_, Logger_.Get()); + if (!r.Read()) { + return {}; + } + + std::pair<TStringBuf, TTvmId> data = ParseTicketsFromDisk(r.Data()); + if (data.second != Settings_.SelfTvmId) { + TStringStream s; + s << "Disk cache is for another tvmId (" << data.second << "). "; + s << "Self=" << Settings_.SelfTvmId; + LogWarning(s.Str()); + return {}; + } + + TThreadedUpdater::TServiceTicketsFromDisk resDst{ + .BornDate = r.Time(), + .FileBody = TString(data.first), + }; + + TThreadedUpdater::TServiceTicketsFromDisk resAll{ + .BornDate = r.Time(), + .FileBody = TString(data.first), + }; + + TDstSetPtr dsts = GetDsts(); + ParseTicketsFromResponse(data.first, *dsts, resDst.TicketsWithErrors); + + if (IsInvalid(TServiceTickets::GetInvalidationTime(resDst.TicketsWithErrors.Tickets), TInstant::Now())) { + LogWarning("Disk cache (service tickets) is too old"); + return {}; + } + + try { + ParseTicketsFromDiskCache(data.first, resAll.TicketsWithErrors); + } catch (std::exception& e) { + LogWarning(TStringBuilder() << "Failed to parse all service tickets from disk cache: " << e.what()); + LogInfo(TStringBuilder() << "Got " << resDst.TicketsWithErrors.Tickets.size() << " service ticket(s) from disk"); + return {std::move(resDst), {}}; + } + + if (resAll.TicketsWithErrors.Tickets.empty()) { + LogInfo(TStringBuilder() << "Got " << resDst.TicketsWithErrors.Tickets.size() << " service ticket(s) from disk"); + return {std::move(resDst), std::move(resAll)}; + } + + if (IsInvalid(TServiceTickets::GetInvalidationTime(resAll.TicketsWithErrors.Tickets), TInstant::Now())) { + LogWarning("Disk cache (service tickets) is too old"); + LogInfo(TStringBuilder() << "Got " << resDst.TicketsWithErrors.Tickets.size() << " service ticket(s) from disk"); + return {std::move(resDst), {}}; + } + + LogInfo(TStringBuilder() << "Got " << resAll.TicketsWithErrors.Tickets.size() << " service ticket(s) from disk"); + return {std::move(resDst), std::move(resAll)}; + } + + std::pair<TString, TInstant> TThreadedUpdater::ReadPublicKeysFromDisk() const { + if (!PublicKeysFilepath_) { + return {}; + } + + TDiskReader r(PublicKeysFilepath_, Logger_.Get()); + if (!r.Read()) { + return {}; + } + + if (TInstant::Now() - r.Time() > PublicKeysDurations_.Invalid) { + LogWarning("Disk cache (public keys) is too old"); + return {}; + } + + return {r.Data(), r.Time()}; + } + + TString TThreadedUpdater::ReadRetrySettingsFromDisk() const { + if (!RetrySettingsFilepath_) { + return {}; + } + + TDiskReader r(RetrySettingsFilepath_, Logger_.Get()); + if (!r.Read()) { + return {}; + } + + return r.Data(); + } + + template <class Dsts> + TThreadedUpdater::THttpResult TThreadedUpdater::GetServiceTicketsFromHttp(const Dsts& dsts, const size_t dstLimit) const { + Y_ENSURE(SigningContext_, "Internal error"); + + TClientSettings::TDstVector part; + part.reserve(dstLimit); + THttpResult res; + res.TicketsWithErrors.Tickets.reserve(dsts.size()); + res.Responses.reserve(dsts.size() / dstLimit + 1); + + for (auto it = dsts.begin(); it != dsts.end();) { + part.clear(); + for (size_t count = 0; it != dsts.end() && count < dstLimit; ++count, ++it) { + part.push_back(*it); + } + + TString response = + FetchWithRetries( + [this, &part]() { + // create request here to keep 'ts' actual + return FetchServiceTicketsFromHttp(PrepareRequestForServiceTickets( + Settings_.SelfTvmId, + *SigningContext_, + part, + ProcInfo_)); + }, + EScope::ServiceTickets) + .Response; + ParseTicketsFromResponse(response, part, res.TicketsWithErrors); + LogDebug(TStringBuilder() + << "Response with service tickets for " << part.size() + << " destination(s) was successfully fetched from " << TvmUrl_); + + res.Responses.push_back(response); + } + + LogDebug(TStringBuilder() + << "Got responses with service tickets with " << res.Responses.size() << " pages for " + << dsts.size() << " destination(s)"); + for (const auto& p : res.TicketsWithErrors.Errors) { + LogError(TStringBuilder() + << "Failed to get service ticket for dst=" << p.first << ": " << p.second); + } + + return res; + } + + TString TThreadedUpdater::GetPublicKeysFromHttp() const { + TString publicKeys = + FetchWithRetries( + [this]() { return FetchPublicKeysFromHttp(); }, + EScope::PublicKeys) + .Response; + + LogDebug("Public keys were successfully fetched from " + TvmUrl_); + + return publicKeys; + } + + TServiceTickets::TMapIdStr TThreadedUpdater::GetRequestedTicketsFromStartUpCache(const TDstSet& dsts) const { + TServiceTickets::TMapIdStr res; + for (const TClientSettings::TDst& dst : dsts) { + auto it = StartUpCache_.TicketsWithErrors.Tickets.find(dst.Id); + if (it != StartUpCache_.TicketsWithErrors.Tickets.end()) { + res[dst.Id] = it->second; + } + } + return res; + } + + TInstant TThreadedUpdater::GetStartUpCacheBornDate() const { + return StartUpCache_.BornDate; + } + + NUtils::TFetchResult TThreadedUpdater::FetchServiceTicketsFromHttp(const TString& body) const { + TStringStream s; + + THttpHeaders outHeaders; + TKeepAliveHttpClient::THttpCode code = GetClient().DoPost("/2/ticket", body, &s, Headers_, &outHeaders); + + const THttpInputHeader* settings = outHeaders.FindHeader("X-Ya-Retry-Settings"); + + return {code, {}, "/2/ticket", s.Str(), settings ? settings->Value() : ""}; + } + + NUtils::TFetchResult TThreadedUpdater::FetchPublicKeysFromHttp() const { + TStringStream s; + + THttpHeaders outHeaders; + TKeepAliveHttpClient::THttpCode code = GetClient().DoGet(PublicKeysUrl_, &s, {}, &outHeaders); + + const THttpInputHeader* settings = outHeaders.FindHeader("X-Ya-Retry-Settings"); + + return {code, {}, "/2/keys", s.Str(), settings ? settings->Value() : ""}; + } + + bool TThreadedUpdater::UpdateRetrySettings(const TString& header) const { + if (header.empty()) { + // Probably it is some kind of test? + return false; + } + + try { + TString raw = NUtils::Base64url2bin(header); + Y_ENSURE(raw, "Invalid base64url in settings"); + + retry_settings::v1::Settings proto; + Y_ENSURE(proto.ParseFromString(raw), "Invalid proto"); + + // This ugly hack helps to process these settings in any case + TThreadedUpdater& this_ = *const_cast<TThreadedUpdater*>(this); + TRetrySettings& res = this_.RetrySettings_; + + TStringStream diff; + auto update = [&diff](auto& l, const auto& r, TStringBuf desc) { + if (l != r) { + diff << desc << ":" << l << "->" << r << ";"; + l = r; + } + }; + + if (proto.has_exponential_backoff_min_sec()) { + update(res.BackoffSettings.Min, + TDuration::Seconds(proto.exponential_backoff_min_sec()), + "exponential_backoff_min"); + } + if (proto.has_exponential_backoff_max_sec()) { + update(res.BackoffSettings.Max, + TDuration::Seconds(proto.exponential_backoff_max_sec()), + "exponential_backoff_max"); + } + if (proto.has_exponential_backoff_factor()) { + update(res.BackoffSettings.Factor, + proto.exponential_backoff_factor(), + "exponential_backoff_factor"); + } + if (proto.has_exponential_backoff_jitter()) { + update(res.BackoffSettings.Jitter, + proto.exponential_backoff_jitter(), + "exponential_backoff_jitter"); + } + this_.ExpBackoff_.UpdateSettings(res.BackoffSettings); + + if (proto.has_max_random_sleep_default()) { + update(res.MaxRandomSleepDefault, + TDuration::MilliSeconds(proto.max_random_sleep_default()), + "max_random_sleep_default"); + } + if (proto.has_max_random_sleep_when_ok()) { + update(res.MaxRandomSleepWhenOk, + TDuration::MilliSeconds(proto.max_random_sleep_when_ok()), + "max_random_sleep_when_ok"); + } + if (proto.has_retries_on_start()) { + Y_ENSURE(proto.retries_on_start(), "retries_on_start==0"); + update(res.RetriesOnStart, + proto.retries_on_start(), + "retries_on_start"); + } + if (proto.has_retries_in_background()) { + Y_ENSURE(proto.retries_in_background(), "retries_in_background==0"); + update(res.RetriesInBackground, + proto.retries_in_background(), + "retries_in_background"); + } + if (proto.has_worker_awaking_period_sec()) { + update(res.WorkerAwakingPeriod, + TDuration::Seconds(proto.worker_awaking_period_sec()), + "worker_awaking_period"); + this_.WorkerAwakingPeriod_ = res.WorkerAwakingPeriod; + } + if (proto.has_dsts_limit()) { + Y_ENSURE(proto.dsts_limit(), "dsts_limit==0"); + update(res.DstsLimit, + proto.dsts_limit(), + "dsts_limit"); + } + + if (proto.has_roles_update_period_sec()) { + Y_ENSURE(proto.roles_update_period_sec(), "roles_update_period==0"); + update(res.RolesUpdatePeriod, + TDuration::Seconds(proto.roles_update_period_sec()), + "roles_update_period_sec"); + } + if (proto.has_roles_warn_period_sec()) { + Y_ENSURE(proto.roles_warn_period_sec(), "roles_warn_period_sec==0"); + update(res.RolesWarnPeriod, + TDuration::Seconds(proto.roles_warn_period_sec()), + "roles_warn_period_sec"); + } + + if (diff.empty()) { + return false; + } + + LogDebug("Retry settings were updated: " + diff.Str()); + return true; + } catch (const std::exception& e) { + LogWarning(TStringBuilder() + << "Failed to update retry settings from server, header '" + << header << "': " + << e.what()); + } + + return false; + } + + template <typename Func> + NUtils::TFetchResult TThreadedUpdater::FetchWithRetries(Func func, EScope scope) const { + const ui32 tries = Inited_ ? RetrySettings_.RetriesInBackground + : RetrySettings_.RetriesOnStart; + + for (size_t idx = 1;; ++idx) { + RandomSleep(); + + try { + NUtils::TFetchResult result = func(); + + if (UpdateRetrySettings(result.RetrySettings) && RetrySettingsFilepath_) { + TDiskWriter w(RetrySettingsFilepath_, Logger_.Get()); + w.Write(result.RetrySettings); + } + + if (400 <= result.Code && result.Code <= 499) { + throw TNonRetriableException() << ProcessHttpError(scope, result.Path, result.Code, result.Response); + } + if (result.Code < 200 || result.Code >= 399) { + throw yexception() << ProcessHttpError(scope, result.Path, result.Code, result.Response); + } + + ExpBackoff_.Decrease(); + return result; + } catch (const TNonRetriableException& e) { + LogWarning(TStringBuilder() << "Failed to get " << scope << ": " << e.what()); + ExpBackoff_.Increase(); + throw; + } catch (const std::exception& e) { + LogWarning(TStringBuilder() << "Failed to get " << scope << ": " << e.what()); + ExpBackoff_.Increase(); + if (idx >= tries) { + throw; + } + } + } + + throw yexception() << "unreachable"; + } + + void TThreadedUpdater::RandomSleep() const { + const TDuration maxSleep = TClientStatus::ECode::Ok == GetState() + ? RetrySettings_.MaxRandomSleepWhenOk + : RetrySettings_.MaxRandomSleepDefault; + + if (maxSleep) { + ui32 toSleep = Random_.GenRand() % maxSleep.MilliSeconds(); + ExpBackoff_.Sleep(TDuration::MilliSeconds(toSleep)); + } + } + + TString TThreadedUpdater::PrepareRequestForServiceTickets(TTvmId src, + const TServiceContext& ctx, + const TClientSettings::TDstVector& dsts, + const NUtils::TProcInfo& procInfo, + time_t now) { + TStringStream s; + + const TString ts = IntToString<10>(now); + TStringStream dst; + dst.Reserve(10 * dsts.size()); + for (const TClientSettings::TDst& d : dsts) { + if (dst.Str()) { + dst << ','; + } + dst << d.Id; + } + + s << "grant_type=client_credentials"; + s << "&src=" << src; + s << "&dst=" << dst.Str(); + s << "&ts=" << ts; + s << "&sign=" << ctx.SignCgiParamsForTvm(ts, dst.Str()); + s << "&get_retry_settings=yes"; + + s << "&"; + procInfo.AddToRequest(s); + + return s.Str(); + } + + template <class Dsts> + void TThreadedUpdater::ParseTicketsFromResponse(TStringBuf resp, + const Dsts& dsts, + TPairTicketsErrors& out) const { + NJson::TJsonValue doc; + Y_ENSURE(NJson::ReadJsonTree(resp, &doc), "Invalid json from tvm-api: " << resp); + const NJson::TJsonValue* currentResp = doc.IsMap() ? &doc : nullptr; + auto find = [¤tResp, &doc](TTvmId id, NJson::TJsonValue& obj) -> bool { + const TString idStr = IntToString<10>(id); + if (currentResp && currentResp->GetValue(idStr, &obj)) { + return true; + } + + for (const NJson::TJsonValue& val : doc.GetArray()) { + currentResp = &val; + if (currentResp->GetValue(idStr, &obj)) { + return true; + } + } + + return false; + }; + + for (const TClientSettings::TDst& d : dsts) { + NJson::TJsonValue obj; + NJson::TJsonValue val; + + if (!find(d.Id, obj) || !obj.GetValue("ticket", &val)) { + TString err; + if (obj.GetValue("error", &val)) { + err = val.GetString(); + } else { + err = "Missing tvm_id in response, should never happend: " + IntToString<10>(d.Id); + } + + TStringStream s; + s << "Failed to get ServiceTicket for " << d.Id << ": " << err; + ProcessError(EType::NonRetriable, EScope::ServiceTickets, s.Str()); + + out.Errors.insert({d.Id, std::move(err)}); + continue; + } + + out.Tickets.insert({d.Id, val.GetString()}); + } + } + + void TThreadedUpdater::ParseTicketsFromDiskCache(TStringBuf cache, + TPairTicketsErrors& out) const { + NJson::TJsonValue doc; + Y_ENSURE(NJson::ReadJsonTree(cache, &doc), "Invalid json from disk: " << cache); + + for (const NJson::TJsonValue& cacheItem : doc.GetArray()) { + for (const auto& [idStr, resp] : cacheItem.GetMap()) { + NJson::TJsonValue val; + TTvmId id; + if (!TryIntFromString<10, TTvmId, TString>(idStr, id)) { + LogWarning(TStringBuilder() << "tvm_id in cache is not integer: " << idStr); + continue; + } + + if (resp.GetValue("ticket", &val)) { + out.Tickets[id] = val.GetString(); + } else if (resp.GetValue("error", &val)) { + out.Errors[id] = val.GetString(); + } else { + out.Errors[id] = "tvm_id found, but response has no error or ticket, should never happend: " + idStr; + } + } + } + } + + static const char DELIMETER = '\t'; + TString TThreadedUpdater::PrepareTicketsForDisk(TStringBuf tvmResponse, TTvmId selfId) { + TStringStream s; + s << tvmResponse << DELIMETER << selfId; + return s.Str(); + } + + std::pair<TStringBuf, TTvmId> TThreadedUpdater::ParseTicketsFromDisk(TStringBuf data) { + TStringBuf tvmId = data.RNextTok(DELIMETER); + return {data, IntFromString<TTvmId, 10>(tvmId)}; + } + + TDstSetPtr TThreadedUpdater::GetDsts() const { + return Destinations_.Get(); + } + + void TThreadedUpdater::SetDsts(TDstSetPtr dsts) { + Destinations_.Set(std::move(dsts)); + } + + bool TThreadedUpdater::IsTimeToUpdateServiceTickets(TInstant lastUpdate) const { + return TInstant::Now() - lastUpdate > ServiceTicketsDurations_.RefreshPeriod; + } + + bool TThreadedUpdater::IsTimeToUpdatePublicKeys(TInstant lastUpdate) const { + return TInstant::Now() - lastUpdate > PublicKeysDurations_.RefreshPeriod; + } + + bool TThreadedUpdater::AreServicesTicketsOk() const { + if (!Settings_.IsServiceTicketFetchingRequired()) { + return true; + } + auto c = GetCachedServiceTickets(); + return c && (!Settings_.IsIncompleteTicketsSetAnError || c->TicketsById.size() == GetDsts()->size()); + } + + bool TThreadedUpdater::IsServiceContextOk() const { + if (!Settings_.CheckServiceTickets) { + return true; + } + + return bool(GetCachedServiceContext()); + } + + bool TThreadedUpdater::IsUserContextOk() const { + if (!Settings_.CheckUserTicketsWithBbEnv) { + return true; + } + return bool(GetCachedUserContext()); + } + + void TThreadedUpdater::Worker() { + UpdateServiceTickets(); + UpdatePublicKeys(); + UpdateRoles(); + } + + TServiceTickets::TMapAliasId TThreadedUpdater::MakeAliasMap(const TClientSettings& settings) { + TServiceTickets::TMapAliasId res; + + for (const auto& pair : settings.FetchServiceTicketsForDstsWithAliases) { + res.insert({pair.first, pair.second.Id}); + } + + return res; + } + + TClientSettings::TDstVector TThreadedUpdater::FindMissingDsts(TServiceTicketsPtr available, const TDstSet& required) { + Y_ENSURE(available); + TDstSet set; + // available->TicketsById is not sorted + for (const auto& pair : available->TicketsById) { + set.insert(pair.first); + } + return FindMissingDsts(set, required); + } + + TClientSettings::TDstVector TThreadedUpdater::FindMissingDsts(const TDstSet& available, const TDstSet& required) { + TClientSettings::TDstVector res; + std::set_difference(required.begin(), required.end(), + available.begin(), available.end(), + std::inserter(res, res.begin())); + return res; + } + + TString TThreadedUpdater::CreateJsonArray(const TSmallVec<TString>& responses) { + if (responses.empty()) { + return "[]"; + } + + size_t size = 0; + for (const TString& r : responses) { + size += r.size() + 1; + } + + TString res; + res.reserve(size + 2); + + res.push_back('['); + for (const TString& r : responses) { + res.append(r).push_back(','); + } + res.back() = ']'; + + return res; + } + + TString TThreadedUpdater::AppendToJsonArray(const TString& json, const TSmallVec<TString>& responses) { + Y_ENSURE(json, "previous body required"); + + size_t size = 0; + for (const TString& r : responses) { + size += r.size() + 1; + } + + TString res; + res.reserve(size + 2 + json.size()); + + res.push_back('['); + if (json.StartsWith('[')) { + Y_ENSURE(json.EndsWith(']'), "array is broken:" << json); + res.append(TStringBuf(json).Chop(1).Skip(1)); + } else { + res.append(json); + } + + res.push_back(','); + for (const TString& r : responses) { + res.append(r).push_back(','); + } + res.back() = ']'; + + return res; + } +} diff --git a/library/cpp/tvmauth/client/misc/api/threaded_updater.h b/library/cpp/tvmauth/client/misc/api/threaded_updater.h new file mode 100644 index 0000000000..cabfcf8fb5 --- /dev/null +++ b/library/cpp/tvmauth/client/misc/api/threaded_updater.h @@ -0,0 +1,153 @@ +#pragma once + +#include "retry_settings.h" +#include "roles_fetcher.h" +#include "settings.h" + +#include <library/cpp/tvmauth/client/misc/async_updater.h> +#include <library/cpp/tvmauth/client/misc/threaded_updater.h> + +#include <util/generic/set.h> +#include <util/random/fast.h> + +#include <mutex> + +namespace NTvmAuth::NTvmApi { + using TDstSet = TSet<TClientSettings::TDst>; + using TDstSetPtr = std::shared_ptr<const TDstSet>; + + class TThreadedUpdater: public TThreadedUpdaterBase { + public: + /*! + * Starts thread for updating of in-memory cache in background + * Reads cache from disk if specified + * @param settings + * @param logger is usefull for monitoring and debuging + */ + static TAsyncUpdaterPtr Create(const TClientSettings& settings, TLoggerPtr logger); + ~TThreadedUpdater(); + + TClientStatus GetStatus() const override; + NRoles::TRolesPtr GetRoles() const override; + + protected: // for tests + TClientStatus::ECode GetState() const; + + TThreadedUpdater(const TClientSettings& settings, TLoggerPtr logger); + void Init(); + + void UpdateServiceTickets(); + void UpdateAllServiceTickets(); + TServiceTicketsPtr UpdateMissingServiceTickets(const TDstSet& required); + void UpdatePublicKeys(); + void UpdateRoles(); + + TServiceTicketsPtr UpdateServiceTicketsCachePartly(TPairTicketsErrors&& tickets, size_t got); + void UpdateServiceTicketsCache(TPairTicketsErrors&& tickets, TInstant time); + void UpdatePublicKeysCache(const TString& publicKeys, TInstant time); + + void ReadStateFromDisk(); + + struct TServiceTicketsFromDisk { + TPairTicketsErrors TicketsWithErrors; + TInstant BornDate; + TString FileBody; + }; + + std::pair<TServiceTicketsFromDisk, TServiceTicketsFromDisk> ReadServiceTicketsFromDisk() const; + + std::pair<TString, TInstant> ReadPublicKeysFromDisk() const; + TString ReadRetrySettingsFromDisk() const; + + struct THttpResult { + TPairTicketsErrors TicketsWithErrors; + TSmallVec<TString> Responses; + }; + + template <class Dsts> + THttpResult GetServiceTicketsFromHttp(const Dsts& dsts, const size_t dstLimit) const; + TString GetPublicKeysFromHttp() const; + TServiceTickets::TMapIdStr GetRequestedTicketsFromStartUpCache(const TDstSet& dsts) const; + + virtual NUtils::TFetchResult FetchServiceTicketsFromHttp(const TString& body) const; + virtual NUtils::TFetchResult FetchPublicKeysFromHttp() const; + + bool UpdateRetrySettings(const TString& header) const; + + template <typename Func> + NUtils::TFetchResult FetchWithRetries(Func func, EScope scope) const; + void RandomSleep() const; + + static TString PrepareRequestForServiceTickets(TTvmId src, + const TServiceContext& ctx, + const TClientSettings::TDstVector& dsts, + const NUtils::TProcInfo& procInfo, + time_t now = time(nullptr)); + template <class Dsts> + void ParseTicketsFromResponse(TStringBuf resp, + const Dsts& dsts, + TPairTicketsErrors& out) const; + + void ParseTicketsFromDiskCache(TStringBuf cache, + TPairTicketsErrors& out) const; + + static TString PrepareTicketsForDisk(TStringBuf tvmResponse, TTvmId selfId); + static std::pair<TStringBuf, TTvmId> ParseTicketsFromDisk(TStringBuf data); + + TDstSetPtr GetDsts() const; + void SetDsts(TDstSetPtr dsts); + + TInstant GetStartUpCacheBornDate() const; + + bool IsTimeToUpdateServiceTickets(TInstant lastUpdate) const; + bool IsTimeToUpdatePublicKeys(TInstant lastUpdate) const; + + bool AreServicesTicketsOk() const; + bool IsServiceContextOk() const; + bool IsUserContextOk() const; + + void Worker() override; + + static TServiceTickets::TMapAliasId MakeAliasMap(const TClientSettings& settings); + static TClientSettings::TDstVector FindMissingDsts(TServiceTicketsPtr available, const TDstSet& required); + static TClientSettings::TDstVector FindMissingDsts(const TDstSet& available, const TDstSet& required); + + static TString CreateJsonArray(const TSmallVec<TString>& responses); + static TString AppendToJsonArray(const TString& json, const TSmallVec<TString>& responses); + + private: + TRetrySettings RetrySettings_; + + protected: + mutable TExponentialBackoff ExpBackoff_; + std::unique_ptr<std::mutex> ServiceTicketBatchUpdateMutex_; + + private: + const TClientSettings Settings_; + + const NUtils::TProcInfo ProcInfo_; + + const TString PublicKeysUrl_; + + const TServiceTickets::TMapAliasId DstAliases_; + + const TKeepAliveHttpClient::THeaders Headers_; + TMaybe<TServiceContext> SigningContext_; + + NUtils::TProtectedValue<TDstSetPtr> Destinations_; + + TString DiskCacheServiceTickets_; + TServiceTicketsFromDisk StartUpCache_; + bool NeedFetchMissingServiceTickets_ = true; + + TString PublicKeysFilepath_; + TString ServiceTicketsFilepath_; + TString RetrySettingsFilepath_; + + std::unique_ptr<TRolesFetcher> RolesFetcher_; + + mutable TReallyFastRng32 Random_; + + bool Inited_ = false; + }; +} diff --git a/library/cpp/tvmauth/client/misc/async_updater.cpp b/library/cpp/tvmauth/client/misc/async_updater.cpp new file mode 100644 index 0000000000..9cb0332ed4 --- /dev/null +++ b/library/cpp/tvmauth/client/misc/async_updater.cpp @@ -0,0 +1,152 @@ +#include "async_updater.h" + +#include "utils.h" + +#include <library/cpp/tvmauth/client/exception.h> + +#include <util/string/builder.h> +#include <util/system/spin_wait.h> + +namespace NTvmAuth { + TAsyncUpdaterBase::TAsyncUpdaterBase() { + ServiceTicketsDurations_.RefreshPeriod = TDuration::Hours(1); + ServiceTicketsDurations_.Expiring = TDuration::Hours(2); + ServiceTicketsDurations_.Invalid = TDuration::Hours(11); + + PublicKeysDurations_.RefreshPeriod = TDuration::Days(1); + PublicKeysDurations_.Expiring = TDuration::Days(2); + PublicKeysDurations_.Invalid = TDuration::Days(6); + } + + NRoles::TRolesPtr TAsyncUpdaterBase::GetRoles() const { + ythrow TIllegalUsage() << "not implemented"; + } + + TInstant TAsyncUpdaterBase::GetUpdateTimeOfPublicKeys() const { + return PublicKeysTime_.Get(); + } + + TInstant TAsyncUpdaterBase::GetUpdateTimeOfServiceTickets() const { + return ServiceTicketsTime_.Get(); + } + + TInstant TAsyncUpdaterBase::GetUpdateTimeOfRoles() const { + return RolesTime_.Get(); + } + + TInstant TAsyncUpdaterBase::GetInvalidationTimeOfPublicKeys() const { + TInstant ins = GetUpdateTimeOfPublicKeys(); + return ins == TInstant() ? TInstant() : ins + PublicKeysDurations_.Invalid; + } + + TInstant TAsyncUpdaterBase::GetInvalidationTimeOfServiceTickets() const { + TServiceTicketsPtr c = GetCachedServiceTickets(); + return c ? c->InvalidationTime : TInstant(); + } + + bool TAsyncUpdaterBase::ArePublicKeysInvalid(TInstant now) const { + return IsInvalid(GetInvalidationTimeOfPublicKeys(), now); + } + + bool TAsyncUpdaterBase::AreServiceTicketsInvalid(TInstant now) const { + TServiceTicketsPtr c = GetCachedServiceTickets(); + // Empty set of tickets is allways valid. + return c && !c->TicketsById.empty() && IsInvalid(GetInvalidationTimeOfServiceTickets(), now); + } + + bool TAsyncUpdaterBase::IsInvalid(TInstant invTime, TInstant now) { + return invTime - + TDuration::Minutes(1) // lag for closing from balancer + < now; + } + + void TAsyncUpdaterBase::SetBbEnv(EBlackboxEnv original, TMaybe<EBlackboxEnv> overrided) { + if (overrided) { + Y_ENSURE_EX(NUtils::CheckBbEnvOverriding(original, *overrided), + TBrokenTvmClientSettings() << "Overriding of BlackboxEnv is illegal: " + << original << " -> " << *overrided); + } + + Envs_.store({original, overrided}, std::memory_order_relaxed); + } + + TServiceTicketsPtr TAsyncUpdaterBase::GetCachedServiceTickets() const { + return ServiceTickets_.Get(); + } + + TServiceContextPtr TAsyncUpdaterBase::GetCachedServiceContext() const { + return ServiceContext_.Get(); + } + + TUserContextPtr TAsyncUpdaterBase::GetCachedUserContext(TMaybe<EBlackboxEnv> overridenEnv) const { + TAllUserContextsPtr ctx = AllUserContexts_.Get(); + if (!ctx) { + return nullptr; + } + + const TEnvs envs = Envs_.load(std::memory_order_relaxed); + if (!envs.Original) { + return nullptr; + } + + EBlackboxEnv env = *envs.Original; + + if (overridenEnv) { + Y_ENSURE_EX(NUtils::CheckBbEnvOverriding(*envs.Original, *overridenEnv), + TBrokenTvmClientSettings() << "Overriding of BlackboxEnv is illegal: " + << *envs.Original << " -> " << *overridenEnv); + env = *overridenEnv; + } else if (envs.Overrided) { + env = *envs.Overrided; + } + + return ctx->Get(env); + } + + void TAsyncUpdaterBase::SetServiceTickets(TServiceTicketsPtr c) { + ServiceTickets_.Set(std::move(c)); + } + + void TAsyncUpdaterBase::SetServiceContext(TServiceContextPtr c) { + ServiceContext_.Set(std::move(c)); + } + + void TAsyncUpdaterBase::SetUserContext(TStringBuf publicKeys) { + AllUserContexts_.Set(MakeIntrusiveConst<TAllUserContexts>(publicKeys)); + } + + void TAsyncUpdaterBase::SetUpdateTimeOfPublicKeys(TInstant ins) { + PublicKeysTime_.Set(ins); + } + + void TAsyncUpdaterBase::SetUpdateTimeOfServiceTickets(TInstant ins) { + ServiceTicketsTime_.Set(ins); + } + + void TAsyncUpdaterBase::SetUpdateTimeOfRoles(TInstant ins) { + RolesTime_.Set(ins); + } + + bool TAsyncUpdaterBase::IsServiceTicketMapOk(TServiceTicketsPtr c, size_t expectedTicketCount, bool strict) { + return c && + (strict + ? c->TicketsById.size() == expectedTicketCount + : !c->TicketsById.empty()); + } + + TAllUserContexts::TAllUserContexts(TStringBuf publicKeys) { + auto add = [&, this](EBlackboxEnv env) { + Ctx_[(size_t)env] = MakeIntrusiveConst<TUserContext>(env, publicKeys); + }; + + add(EBlackboxEnv::Prod); + add(EBlackboxEnv::Test); + add(EBlackboxEnv::ProdYateam); + add(EBlackboxEnv::TestYateam); + add(EBlackboxEnv::Stress); + } + + TUserContextPtr TAllUserContexts::Get(EBlackboxEnv env) const { + return Ctx_[(size_t)env]; + } +} diff --git a/library/cpp/tvmauth/client/misc/async_updater.h b/library/cpp/tvmauth/client/misc/async_updater.h new file mode 100644 index 0000000000..68c0c11cf0 --- /dev/null +++ b/library/cpp/tvmauth/client/misc/async_updater.h @@ -0,0 +1,114 @@ +#pragma once + +#include "last_error.h" +#include "service_tickets.h" +#include "settings.h" +#include "roles/roles.h" + +#include <library/cpp/tvmauth/client/client_status.h> +#include <library/cpp/tvmauth/client/logger.h> + +#include <library/cpp/tvmauth/deprecated/service_context.h> +#include <library/cpp/tvmauth/deprecated/user_context.h> +#include <library/cpp/tvmauth/src/utils.h> + +#include <util/datetime/base.h> +#include <util/generic/hash.h> +#include <util/generic/maybe.h> +#include <util/generic/noncopyable.h> +#include <util/generic/ptr.h> + +#include <array> +#include <atomic> + +namespace NTvmAuth { + + class TAllUserContexts: public TAtomicRefCount<TAllUserContexts> { + public: + TAllUserContexts(TStringBuf publicKeys); + + TUserContextPtr Get(EBlackboxEnv env) const; + + private: + std::array<TUserContextPtr, 5> Ctx_; + }; + using TAllUserContextsPtr = TIntrusiveConstPtr<TAllUserContexts>; + + class TAsyncUpdaterBase: public TAtomicRefCount<TAsyncUpdaterBase>, protected TLastError, TNonCopyable { + public: + TAsyncUpdaterBase(); + virtual ~TAsyncUpdaterBase() = default; + + virtual TClientStatus GetStatus() const = 0; + virtual NRoles::TRolesPtr GetRoles() const; + + TServiceTicketsPtr GetCachedServiceTickets() const; + TServiceContextPtr GetCachedServiceContext() const; + TUserContextPtr GetCachedUserContext(TMaybe<EBlackboxEnv> overridenEnv = {}) const; + + TInstant GetUpdateTimeOfPublicKeys() const; + TInstant GetUpdateTimeOfServiceTickets() const; + TInstant GetUpdateTimeOfRoles() const; + TInstant GetInvalidationTimeOfPublicKeys() const; + TInstant GetInvalidationTimeOfServiceTickets() const; + + bool ArePublicKeysInvalid(TInstant now) const; + bool AreServiceTicketsInvalid(TInstant now) const; + static bool IsInvalid(TInstant invTime, TInstant now); + + protected: + void SetBbEnv(EBlackboxEnv original, TMaybe<EBlackboxEnv> overrided = {}); + + void SetServiceTickets(TServiceTicketsPtr c); + void SetServiceContext(TServiceContextPtr c); + void SetUserContext(TStringBuf publicKeys); + void SetUpdateTimeOfPublicKeys(TInstant ins); + void SetUpdateTimeOfServiceTickets(TInstant ins); + void SetUpdateTimeOfRoles(TInstant ins); + + static bool IsServiceTicketMapOk(TServiceTicketsPtr c, size_t expectedTicketCount, bool strict); + + protected: + struct TPairTicketsErrors { + TServiceTickets::TMapIdStr Tickets; + TServiceTickets::TMapIdStr Errors; + + bool operator==(const TPairTicketsErrors& o) const { + return Tickets == o.Tickets && Errors == o.Errors; + } + }; + + struct TStateDurations { + TDuration RefreshPeriod; + TDuration Expiring; + TDuration Invalid; + }; + + TStateDurations ServiceTicketsDurations_; + TStateDurations PublicKeysDurations_; + + protected: + virtual void StartTvmClientStopping() const { + } + virtual bool IsTvmClientStopped() const { + return true; + } + friend class NTvmAuth::NInternal::TClientCaningKnife; + + private: + struct TEnvs { + TMaybe<EBlackboxEnv> Original; + TMaybe<EBlackboxEnv> Overrided; + }; + static_assert(sizeof(TEnvs) <= 8, "Small struct is easy to store as atomic"); + std::atomic<TEnvs> Envs_ = {{}}; + + NUtils::TProtectedValue<TServiceTicketsPtr> ServiceTickets_; + NUtils::TProtectedValue<TServiceContextPtr> ServiceContext_; + NUtils::TProtectedValue<TAllUserContextsPtr> AllUserContexts_; + NUtils::TProtectedValue<TInstant> PublicKeysTime_; + NUtils::TProtectedValue<TInstant> ServiceTicketsTime_; + NUtils::TProtectedValue<TInstant> RolesTime_; + }; + using TAsyncUpdaterPtr = TIntrusiveConstPtr<TAsyncUpdaterBase>; +} diff --git a/library/cpp/tvmauth/client/misc/checker.h b/library/cpp/tvmauth/client/misc/checker.h new file mode 100644 index 0000000000..16f1a95200 --- /dev/null +++ b/library/cpp/tvmauth/client/misc/checker.h @@ -0,0 +1,38 @@ +#pragma once + +#include <library/cpp/tvmauth/client/exception.h> + +#include <library/cpp/tvmauth/checked_service_ticket.h> +#include <library/cpp/tvmauth/checked_user_ticket.h> +#include <library/cpp/tvmauth/deprecated/service_context.h> +#include <library/cpp/tvmauth/deprecated/user_context.h> + +namespace NTvmAuth { + class TServiceTicketChecker { + public: + /*! + * Checking must be enabled in TClientSettings + * Can throw exception if cache is out of date or wrong config + * @param ticket + */ + static TCheckedServiceTicket Check( + TStringBuf ticket, + TServiceContextPtr c, + const TServiceContext::TCheckFlags& flags = {}) { + Y_ENSURE_EX(c, TBrokenTvmClientSettings() << "Need to use TClientSettings::EnableServiceTicketChecking()"); + return c->Check(ticket, flags); + } + }; + + class TUserTicketChecker { + public: + /*! + * Blackbox enviroment must be cofingured in TClientSettings + * Can throw exception if cache is out of date or wrong config + */ + static TCheckedUserTicket Check(TStringBuf ticket, TUserContextPtr c) { + Y_ENSURE_EX(c, TBrokenTvmClientSettings() << "Need to use TClientSettings::EnableUserTicketChecking()"); + return c->Check(ticket); + } + }; +} diff --git a/library/cpp/tvmauth/client/misc/default_uid_checker.h b/library/cpp/tvmauth/client/misc/default_uid_checker.h new file mode 100644 index 0000000000..b723d6e918 --- /dev/null +++ b/library/cpp/tvmauth/client/misc/default_uid_checker.h @@ -0,0 +1,31 @@ +#pragma once + +#include "roles/roles.h" + +#include <library/cpp/tvmauth/client/exception.h> + +#include <library/cpp/tvmauth/checked_user_ticket.h> +#include <library/cpp/tvmauth/src/user_impl.h> +#include <library/cpp/tvmauth/src/utils.h> + +namespace NTvmAuth { + class TDefaultUidChecker { + public: + /*! + * Checking must be enabled in TClientSettings + * Can throw exception if cache is out of date or wrong config + * @param ticket + */ + static TCheckedUserTicket Check(TCheckedUserTicket ticket, NRoles::TRolesPtr r) { + Y_ENSURE_EX(r, TBrokenTvmClientSettings() << "Need to use TClientSettings::EnableRolesFetching()"); + NRoles::TConsumerRolesPtr roles = r->GetRolesForUser(ticket); + if (roles) { + return ticket; + } + + TUserTicketImplPtr impl = THolder(NInternal::TCanningKnife::GetU(ticket)); + impl->SetStatus(ETicketStatus::NoRoles); + return TCheckedUserTicket(std::move(impl)); + } + }; +} diff --git a/library/cpp/tvmauth/client/misc/disk_cache.cpp b/library/cpp/tvmauth/client/misc/disk_cache.cpp new file mode 100644 index 0000000000..8f3ab7770f --- /dev/null +++ b/library/cpp/tvmauth/client/misc/disk_cache.cpp @@ -0,0 +1,162 @@ +#include "disk_cache.h" + +#include <library/cpp/tvmauth/client/logger.h> + +#include <openssl/evp.h> +#include <openssl/hmac.h> + +#include <util/stream/file.h> +#include <util/stream/str.h> +#include <util/system/fs.h> +#include <util/system/sysstat.h> + +#include <exception> + +namespace NTvmAuth { + static const size_t HASH_SIZE = 32; + static const size_t TIMESTAMP_SIZE = sizeof(time_t); + + TDiskReader::TDiskReader(const TString& filename, ILogger* logger) + : Filename_(filename) + , Logger_(logger) + { + } + + bool TDiskReader::Read() { + TStringStream s; + + try { + if (!NFs::Exists(Filename_)) { + if (Logger_) { + s << "File '" << Filename_ << "' does not exist"; + Logger_->Debug(s.Str()); + } + return false; + } + + TFile file(Filename_, OpenExisting | RdOnly | Seq); + file.Flock(LOCK_SH | LOCK_NB); + + TFileInput input(file); + return ParseData(input.ReadAll()); + } catch (const std::exception& e) { + if (Logger_) { + s << "Failed to read '" << Filename_ << "': " << e.what(); + Logger_->Error(s.Str()); + } + } + + return false; + } + + bool TDiskReader::ParseData(TStringBuf buf) { + TStringStream s; + + if (buf.size() <= HASH_SIZE + TIMESTAMP_SIZE) { + if (Logger_) { + s << "File '" << Filename_ << "' is too small"; + Logger_->Warning(s.Str()); + } + return false; + } + + TStringBuf hash = buf.SubStr(0, HASH_SIZE); + if (hash != GetHash(buf.Skip(HASH_SIZE))) { + if (Logger_) { + s << "Content of '" << Filename_ << "' was incorrectly changed"; + Logger_->Warning(s.Str()); + } + return false; + } + + Time_ = TInstant::Seconds(GetTimestamp(buf.substr(0, TIMESTAMP_SIZE))); + Data_ = buf.Skip(TIMESTAMP_SIZE); + + if (Logger_) { + s << "File '" << Filename_ << "' was successfully read"; + Logger_->Info(s.Str()); + } + return true; + } + + TString TDiskReader::GetHash(TStringBuf data) { + TString value(EVP_MAX_MD_SIZE, 0); + unsigned macLen = 0; + if (!::HMAC(EVP_sha256(), + "", + 0, + (unsigned char*)data.data(), + data.size(), + (unsigned char*)value.data(), + &macLen)) { + return {}; + } + + if (macLen != EVP_MAX_MD_SIZE) { + value.resize(macLen); + } + + return value; + } + + time_t TDiskReader::GetTimestamp(TStringBuf data) { + time_t time = 0; + for (int idx = TIMESTAMP_SIZE - 1; idx >= 0; --idx) { + time <<= 8; + time |= static_cast<unsigned char>(data.at(idx)); + } + return time; + } + + TDiskWriter::TDiskWriter(const TString& filename, ILogger* logger) + : Filename_(filename) + , Logger_(logger) + { + } + + bool TDiskWriter::Write(TStringBuf data, TInstant now) { + TStringStream s; + + try { + { + if (NFs::Exists(Filename_)) { + Chmod(Filename_.c_str(), + S_IRUSR | S_IWUSR); // 600 + } + + TFile file(Filename_, CreateAlways | WrOnly | Seq | AWUser | ARUser); + file.Flock(LOCK_EX | LOCK_NB); + + TFileOutput output(file); + output << PrepareData(now, data); + } + + if (Logger_) { + s << "File '" << Filename_ << "' was successfully written"; + Logger_->Info(s.Str()); + } + return true; + } catch (const std::exception& e) { + if (Logger_) { + s << "Failed to write '" << Filename_ << "': " << e.what(); + Logger_->Error(s.Str()); + } + } + + return false; + } + + TString TDiskWriter::PrepareData(TInstant time, TStringBuf data) { + TString toHash = WriteTimestamp(time.TimeT()) + data; + return TDiskReader::GetHash(toHash) + toHash; + } + + TString TDiskWriter::WriteTimestamp(time_t time) { + TString res(TIMESTAMP_SIZE, 0); + for (size_t idx = 0; idx < TIMESTAMP_SIZE; ++idx) { + res[idx] = time & 0xFF; + time >>= 8; + } + return res; + } +} diff --git a/library/cpp/tvmauth/client/misc/disk_cache.h b/library/cpp/tvmauth/client/misc/disk_cache.h new file mode 100644 index 0000000000..9e77556f86 --- /dev/null +++ b/library/cpp/tvmauth/client/misc/disk_cache.h @@ -0,0 +1,50 @@ +#pragma once + +#include <util/datetime/base.h> +#include <util/generic/string.h> + +namespace NTvmAuth { + class ILogger; + + class TDiskReader { + public: + TDiskReader(const TString& filename, ILogger* logger = nullptr); + + bool Read(); + + const TString& Data() const { + return Data_; + } + + TInstant Time() const { + return Time_; + } + + public: // for tests + bool ParseData(TStringBuf buf); + + static TString GetHash(TStringBuf data); + static time_t GetTimestamp(TStringBuf data); + + private: + TString Filename_; + ILogger* Logger_; + TInstant Time_; + TString Data_; + }; + + class TDiskWriter { + public: + TDiskWriter(const TString& filename, ILogger* logger = nullptr); + + bool Write(TStringBuf data, TInstant now = TInstant::Now()); + + public: // for tests + static TString PrepareData(TInstant time, TStringBuf data); + static TString WriteTimestamp(time_t time); + + private: + TString Filename_; + ILogger* Logger_; + }; +} diff --git a/library/cpp/tvmauth/client/misc/exponential_backoff.h b/library/cpp/tvmauth/client/misc/exponential_backoff.h new file mode 100644 index 0000000000..89a7a3c8ad --- /dev/null +++ b/library/cpp/tvmauth/client/misc/exponential_backoff.h @@ -0,0 +1,94 @@ +#pragma once + +#include <util/datetime/base.h> +#include <util/random/normal.h> +#include <util/system/event.h> + +#include <atomic> + +namespace NTvmAuth { + // https://habr.com/ru/post/227225/ + class TExponentialBackoff { + public: + struct TSettings { + TDuration Min; + TDuration Max; + double Factor = 1.001; + double Jitter = 0; + + bool operator==(const TSettings& o) const { + return Min == o.Min && + Max == o.Max && + Factor == o.Factor && + Jitter == o.Jitter; + } + }; + + TExponentialBackoff(const TSettings& settings, bool isEnabled = true) + : CurrentValue_(settings.Min) + , IsEnabled_(isEnabled) + { + UpdateSettings(settings); + } + + void UpdateSettings(const TSettings& settings) { + Y_ENSURE(settings.Factor > 1, "factor=" << settings.Factor << ". Should be > 1"); + Y_ENSURE(settings.Jitter >= 0 && settings.Jitter < 1, "jitter should be in range [0, 1)"); + + Min_ = settings.Min; + Max_ = settings.Max; + Factor_ = settings.Factor; + Jitter_ = settings.Jitter; + } + + TDuration Increase() { + CurrentValue_ = std::min(CurrentValue_ * Factor_, Max_); + + double rnd = StdNormalRandom<double>(); + const bool isNegative = rnd < 0; + rnd = std::abs(rnd); + + const TDuration diff = rnd * Jitter_ * CurrentValue_; + if (isNegative) { + CurrentValue_ -= diff; + } else { + CurrentValue_ += diff; + } + + return CurrentValue_; + } + + TDuration Decrease() { + CurrentValue_ = std::max(CurrentValue_ / Factor_, Min_); + return CurrentValue_; + } + + void Sleep(TDuration add = TDuration()) { + if (IsEnabled_.load(std::memory_order_relaxed)) { + Ev_.WaitT(CurrentValue_ + add); + } + } + + void Interrupt() { + Ev_.Signal(); + } + + TDuration GetCurrentValue() const { + return CurrentValue_; + } + + void SetEnabled(bool val) { + IsEnabled_.store(val); + } + + private: + TDuration Min_; + TDuration Max_; + double Factor_; + double Jitter_; + TDuration CurrentValue_; + std::atomic_bool IsEnabled_; + + TAutoEvent Ev_; + }; +} diff --git a/library/cpp/tvmauth/client/misc/fetch_result.h b/library/cpp/tvmauth/client/misc/fetch_result.h new file mode 100644 index 0000000000..4b0774e92f --- /dev/null +++ b/library/cpp/tvmauth/client/misc/fetch_result.h @@ -0,0 +1,13 @@ +#pragma once + +#include <library/cpp/http/simple/http_client.h> + +namespace NTvmAuth::NUtils { + struct TFetchResult { + TKeepAliveHttpClient::THttpCode Code; + THttpHeaders Headers; + TStringBuf Path; + TString Response; + TString RetrySettings; + }; +} diff --git a/library/cpp/tvmauth/client/misc/getter.h b/library/cpp/tvmauth/client/misc/getter.h new file mode 100644 index 0000000000..6c7617b418 --- /dev/null +++ b/library/cpp/tvmauth/client/misc/getter.h @@ -0,0 +1,49 @@ +#pragma once + +#include "checker.h" +#include "service_tickets.h" + +namespace NTvmAuth { + class TServiceTicketGetter { + public: + /*! + * Fetching must enabled in TClientSettings + * Can throw exception if cache is invalid or wrong config + * @param dst + */ + static TString GetTicket(const TClientSettings::TAlias& dst, TServiceTicketsPtr c) { + Y_ENSURE_EX(c, TBrokenTvmClientSettings() << "Need to use TClientSettings::EnableServiceTicketsFetchOptions()"); + return GetTicketImpl(dst, c->TicketsByAlias, c->ErrorsByAlias, c->UnfetchedAliases); + } + + static TString GetTicket(const TTvmId dst, TServiceTicketsPtr c) { + Y_ENSURE_EX(c, TBrokenTvmClientSettings() << "Need to use TClientSettings::EnableServiceTicketsFetchOptions()"); + return GetTicketImpl(dst, c->TicketsById, c->ErrorsById, c->UnfetchedIds); + } + + private: + template <class Key, class Cont, class UnfetchedCont> + static TString GetTicketImpl(const Key& dst, const Cont& tickets, const Cont& errors, const UnfetchedCont& unfetched) { + auto it = tickets.find(dst); + if (it != tickets.end()) { + return it->second; + } + + it = errors.find(dst); + if (it != errors.end()) { + ythrow TMissingServiceTicket() + << "Failed to get ticket for '" << dst << "': " + << it->second; + } + + if (unfetched.contains(dst)) { + ythrow TMissingServiceTicket() + << "Failed to get ticket for '" << dst << "': this dst was not fetched yet."; + } + + ythrow TBrokenTvmClientSettings() + << "Destination '" << dst << "' was not specified in settings. " + << "Check your settings (if you use Qloud/YP/tvmtool - check it's settings)"; + } + }; +} diff --git a/library/cpp/tvmauth/client/misc/last_error.cpp b/library/cpp/tvmauth/client/misc/last_error.cpp new file mode 100644 index 0000000000..a6279bb1ef --- /dev/null +++ b/library/cpp/tvmauth/client/misc/last_error.cpp @@ -0,0 +1,115 @@ +#include "last_error.h" + +#include <util/string/builder.h> + +namespace NTvmAuth { + TLastError::TLastError() + : LastErrors_(MakeIntrusiveConst<TLastErrors>()) + { + } + + TString TLastError::GetLastError(bool isOk, EType* type) const { + if (isOk) { + return OK_; + } + + const TLastErrorsPtr ptr = LastErrors_.Get(); + + for (const TLastErr& err : ptr->Errors) { + if (err && err->first == EType::NonRetriable) { + if (type) { + *type = EType::NonRetriable; + } + return err->second; + } + } + + for (const TLastErr& err : ptr->Errors) { + if (err) { + if (type) { + *type = EType::Retriable; + } + return err->second; + } + } + + if (type) { + *type = EType::NonRetriable; + } + return "Internal client error: failed to collect last useful error message, please report this message to tvm-dev@yandex-team.ru"; + } + + TString TLastError::ProcessHttpError(TLastError::EScope scope, + TStringBuf path, + int code, + const TString& msg) const { + TString err = TStringBuilder() << "Path:" << path << ".Code=" << code << ": " << msg; + + ProcessError(code >= 400 && code < 500 ? EType::NonRetriable + : EType::Retriable, + scope, + err); + + return err; + } + + void TLastError::ProcessError(TLastError::EType type, TLastError::EScope scope, const TStringBuf msg) const { + Update(scope, [&](TLastErr& lastError) { + if (lastError && lastError->first == EType::NonRetriable && type == EType::Retriable) { + return false; + } + + TString err = TStringBuilder() << scope << ": " << msg; + err.erase(std::remove(err.begin(), err.vend(), '\r'), err.vend()); + std::replace(err.begin(), err.vend(), '\n', ' '); + + lastError = {type, std::move(err)}; + return true; + }); + } + + void TLastError::ClearError(TLastError::EScope scope) { + Update(scope, [&](TLastErr& lastError) { + if (!lastError) { + return false; + } + + lastError.Clear(); + return true; + }); + } + + void TLastError::ClearErrors() { + for (size_t idx = 0; idx < (size_t)EScope::COUNT; ++idx) { + ClearError((EScope)idx); + } + } + + void TLastError::ThrowLastError() { + EType type; + TString err = GetLastError(false, &type); + + switch (type) { + case EType::NonRetriable: + ythrow TNonRetriableException() + << "Failed to start TvmClient. Do not retry: " + << err; + case EType::Retriable: + ythrow TRetriableException() + << "Failed to start TvmClient. You can retry: " + << err; + } + } + + template <typename Func> + void TLastError::Update(TLastError::EScope scope, Func func) const { + Y_VERIFY(scope != EScope::COUNT); + + TLastErrors errs = *LastErrors_.Get(); + TLastErr& lastError = errs.Errors[(size_t)scope]; + + if (func(lastError)) { + LastErrors_.Set(MakeIntrusiveConst<TLastErrors>(std::move(errs))); + } + } +} diff --git a/library/cpp/tvmauth/client/misc/last_error.h b/library/cpp/tvmauth/client/misc/last_error.h new file mode 100644 index 0000000000..b0ad33611f --- /dev/null +++ b/library/cpp/tvmauth/client/misc/last_error.h @@ -0,0 +1,51 @@ +#pragma once + +#include "utils.h" + +#include <array> + +namespace NTvmAuth { + class TLastError { + public: + enum class EType { + NonRetriable, + Retriable, + }; + + enum class EScope { + ServiceTickets, + PublicKeys, + Roles, + TvmtoolConfig, + + COUNT, + }; + + using TLastErr = TMaybe<std::pair<EType, TString>>; + + struct TLastErrors: public TAtomicRefCount<TLastErrors> { + std::array<TLastErr, (int)EScope::COUNT> Errors; + }; + using TLastErrorsPtr = TIntrusiveConstPtr<TLastErrors>; + + public: + TLastError(); + + TString GetLastError(bool isOk, EType* type = nullptr) const; + + TString ProcessHttpError(EScope scope, TStringBuf path, int code, const TString& msg) const; + void ProcessError(EType type, EScope scope, const TStringBuf msg) const; + void ClearError(EScope scope); + void ClearErrors(); + void ThrowLastError(); + + private: + template <typename Func> + void Update(EScope scope, Func func) const; + + private: + const TString OK_ = "OK"; + + mutable NUtils::TProtectedValue<TLastErrorsPtr> LastErrors_; + }; +} diff --git a/library/cpp/tvmauth/client/misc/proc_info.cpp b/library/cpp/tvmauth/client/misc/proc_info.cpp new file mode 100644 index 0000000000..e2e5ec15b9 --- /dev/null +++ b/library/cpp/tvmauth/client/misc/proc_info.cpp @@ -0,0 +1,53 @@ +#include "proc_info.h" + +#include <library/cpp/tvmauth/version.h> + +#include <library/cpp/string_utils/quote/quote.h> + +#include <util/stream/file.h> +#include <util/string/cast.h> +#include <util/system/getpid.h> + +namespace NTvmAuth::NUtils { + void TProcInfo::AddToRequest(IOutputStream& out) const { + out << "_pid=" << Pid; + if (ProcessName) { + out << "&_procces_name=" << *ProcessName; + } + out << "&lib_version=client_" << VersionPrefix << LibVersion(); + } + + TProcInfo TProcInfo::Create(const TString& versionPrefix) { + TProcInfo res; + res.Pid = IntToString<10>(GetPID()); + res.ProcessName = GetProcessName(); + res.VersionPrefix = versionPrefix; + return res; + } + + std::optional<TString> TProcInfo::GetProcessName() { + try { + // works only for linux + TFileInput proc("/proc/self/status"); + + TString line; + while (proc.ReadLine(line)) { + TStringBuf buf(line); + if (!buf.SkipPrefix("Name:")) { + continue; + } + + while (buf && isspace(buf.front())) { + buf.Skip(1); + } + + TString res(buf); + CGIEscape(res); + return res; + } + } catch (...) { + } + + return {}; + } +} diff --git a/library/cpp/tvmauth/client/misc/proc_info.h b/library/cpp/tvmauth/client/misc/proc_info.h new file mode 100644 index 0000000000..b1526e5c47 --- /dev/null +++ b/library/cpp/tvmauth/client/misc/proc_info.h @@ -0,0 +1,18 @@ +#pragma once + +#include <util/generic/string.h> + +#include <optional> + +namespace NTvmAuth::NUtils { + struct TProcInfo { + TString Pid; + std::optional<TString> ProcessName; + TString VersionPrefix; + + void AddToRequest(IOutputStream& out) const; + + static TProcInfo Create(const TString& versionPrefix); + static std::optional<TString> GetProcessName(); + }; +} diff --git a/library/cpp/tvmauth/client/misc/retry_settings/v1/settings.proto b/library/cpp/tvmauth/client/misc/retry_settings/v1/settings.proto new file mode 100644 index 0000000000..72817847a6 --- /dev/null +++ b/library/cpp/tvmauth/client/misc/retry_settings/v1/settings.proto @@ -0,0 +1,21 @@ +syntax = "proto2"; + +package retry_settings.v1; + +option cc_enable_arenas = true; +option go_package = "a.yandex-team.ru/library/cpp/tvmauth/client/misc/retry_settings/v1"; + +message Settings { + optional uint32 exponential_backoff_min_sec = 1; + optional uint32 exponential_backoff_max_sec = 2; + optional double exponential_backoff_factor = 3; + optional double exponential_backoff_jitter = 4; + optional uint32 max_random_sleep_default = 5; + optional uint32 max_random_sleep_when_ok = 12; + optional uint32 retries_on_start = 6; + optional uint32 worker_awaking_period_sec = 7; + optional uint32 dsts_limit = 8; + optional uint32 retries_in_background = 9; + optional uint32 roles_update_period_sec = 10; + optional uint32 roles_warn_period_sec = 11; +} diff --git a/library/cpp/tvmauth/client/misc/roles/decoder.cpp b/library/cpp/tvmauth/client/misc/roles/decoder.cpp new file mode 100644 index 0000000000..6337fb91c2 --- /dev/null +++ b/library/cpp/tvmauth/client/misc/roles/decoder.cpp @@ -0,0 +1,93 @@ +#include "decoder.h" + +#include <library/cpp/tvmauth/client/misc/utils.h> + +#include <library/cpp/openssl/crypto/sha.h> +#include <library/cpp/streams/brotli/brotli.h> +#include <library/cpp/streams/zstd/zstd.h> + +#include <util/generic/yexception.h> +#include <util/stream/zlib.h> +#include <util/string/ascii.h> + +namespace NTvmAuth::NRoles { + TString TDecoder::Decode(const TStringBuf codec, TString&& blob) { + if (codec.empty()) { + return std::move(blob); + } + + const TCodecInfo info = ParseCodec(codec); + TString decoded = DecodeImpl(info.Type, blob); + + VerifySize(decoded, info.Size); + VerifyChecksum(decoded, info.Sha256); + + return decoded; + } + + TDecoder::TCodecInfo TDecoder::ParseCodec(TStringBuf codec) { + const char delim = ':'; + + const TStringBuf version = codec.NextTok(delim); + Y_ENSURE(version == "1", + "unknown codec format version; known: 1; got: " << version); + + TCodecInfo res; + res.Type = codec.NextTok(delim); + Y_ENSURE(res.Type, "codec type is empty"); + + const TStringBuf size = codec.NextTok(delim); + Y_ENSURE(TryIntFromString<10>(size, res.Size), + "decoded blob size is not number"); + + res.Sha256 = codec; + const size_t expectedSha256Size = 2 * NOpenSsl::NSha256::DIGEST_LENGTH; + Y_ENSURE(res.Sha256.size() == expectedSha256Size, + "sha256 of decoded blob has invalid length: expected " + << expectedSha256Size << ", got " << res.Sha256.size()); + + return res; + } + + TString TDecoder::DecodeImpl(TStringBuf codec, const TString& blob) { + if (AsciiEqualsIgnoreCase(codec, "brotli")) { + return DecodeBrolti(blob); + } else if (AsciiEqualsIgnoreCase(codec, "gzip")) { + return DecodeGzip(blob); + } else if (AsciiEqualsIgnoreCase(codec, "zstd")) { + return DecodeZstd(blob); + } + + ythrow yexception() << "unknown codec: '" << codec << "'"; + } + + TString TDecoder::DecodeBrolti(const TString& blob) { + TStringInput in(blob); + return TBrotliDecompress(&in).ReadAll(); + } + + TString TDecoder::DecodeGzip(const TString& blob) { + TStringInput in(blob); + return TZLibDecompress(&in).ReadAll(); + } + + TString TDecoder::DecodeZstd(const TString& blob) { + TStringInput in(blob); + return TZstdDecompress(&in).ReadAll(); + } + + void TDecoder::VerifySize(const TStringBuf decoded, size_t expected) { + Y_ENSURE(expected == decoded.size(), + "Decoded blob has bad size: expected " << expected << ", actual " << decoded.size()); + } + + void TDecoder::VerifyChecksum(const TStringBuf decoded, const TStringBuf expected) { + using namespace NOpenSsl::NSha256; + + const TDigest dig = Calc(decoded); + const TString actual = NUtils::ToHex(TStringBuf((char*)dig.data(), dig.size())); + + Y_ENSURE(AsciiEqualsIgnoreCase(actual, expected), + "Decoded blob has bad sha256: expected=" << expected << ", actual=" << actual); + } +} diff --git a/library/cpp/tvmauth/client/misc/roles/decoder.h b/library/cpp/tvmauth/client/misc/roles/decoder.h new file mode 100644 index 0000000000..de5cdb37e0 --- /dev/null +++ b/library/cpp/tvmauth/client/misc/roles/decoder.h @@ -0,0 +1,32 @@ +#pragma once + +#include <util/generic/string.h> + +namespace NTvmAuth::NRoles { + class TDecoder { + public: + static TString Decode(const TStringBuf codec, TString&& blob); + + public: + struct TCodecInfo { + TStringBuf Type; + size_t Size = 0; + TStringBuf Sha256; + + bool operator==(const TCodecInfo& o) const { + return Type == o.Type && + Size == o.Size && + Sha256 == o.Sha256; + } + }; + + static TCodecInfo ParseCodec(TStringBuf codec); + static TString DecodeImpl(TStringBuf codec, const TString& blob); + static TString DecodeBrolti(const TString& blob); + static TString DecodeGzip(const TString& blob); + static TString DecodeZstd(const TString& blob); + + static void VerifySize(const TStringBuf decoded, size_t expected); + static void VerifyChecksum(const TStringBuf decoded, const TStringBuf expected); + }; +} diff --git a/library/cpp/tvmauth/client/misc/roles/entities_index.cpp b/library/cpp/tvmauth/client/misc/roles/entities_index.cpp new file mode 100644 index 0000000000..c9b72c3a17 --- /dev/null +++ b/library/cpp/tvmauth/client/misc/roles/entities_index.cpp @@ -0,0 +1,114 @@ +#include "entities_index.h" + +#include <util/stream/str.h> + +#include <set> + +namespace NTvmAuth::NRoles { + TEntitiesIndex::TStage::TStage(const std::set<TString>& k) + : Keys_(k.begin(), k.end()) + { + } + + // TODO TStringBuf + bool TEntitiesIndex::TStage::GetNextKeySet(std::vector<TString>& out) { + out.clear(); + out.reserve(Keys_.size()); + + ++Id_; + for (size_t idx = 0; idx < Keys_.size(); ++idx) { + bool need = (Id_ >> idx) & 0x01; + + if (need) { + out.push_back(Keys_[idx]); + } + } + + return !out.empty(); + } + + TEntitiesIndex::TEntitiesIndex(const std::vector<TEntityPtr>& entities) { + const std::set<TString> uniqueKeys = GetUniqueSortedKeys(entities); + Idx_.Entities = entities; + Idx_.SubTree.reserve(uniqueKeys.size() * entities.size()); + + TStage stage(uniqueKeys); + std::vector<TString> keyset; + while (stage.GetNextKeySet(keyset)) { + for (const TEntityPtr& e : entities) { + TSubTree* currentBranch = &Idx_; + + for (const TString& key : keyset) { + auto it = e->find(key); + if (it == e->end()) { + continue; + } + + auto [i, ok] = currentBranch->SubTree.emplace( + TKeyValue{it->first, it->second}, + TSubTree()); + + currentBranch = &i->second; + currentBranch->Entities.push_back(e); + } + } + } + + MakeUnique(Idx_); + } + + std::set<TString> TEntitiesIndex::GetUniqueSortedKeys(const std::vector<TEntityPtr>& entities) { + std::set<TString> res; + + for (const TEntityPtr& e : entities) { + for (const auto& [key, value] : *e) { + res.insert(key); + } + } + + return res; + } + + void TEntitiesIndex::MakeUnique(TSubTree& branch) { + auto& vec = branch.Entities; + std::sort(vec.begin(), vec.end()); + vec.erase(std::unique(vec.begin(), vec.end()), vec.end()); + + for (auto& [_, restPart] : branch.SubTree) { + MakeUnique(restPart); + } + } + + static void Print(const TEntitiesIndex::TSubTree& part, IOutputStream& out, size_t offset = 0) { + std::vector<std::pair<TKeyValue, const TEntitiesIndex::TSubTree*>> vec; + vec.reserve(part.SubTree.size()); + + for (const auto& [key, value] : part.SubTree) { + vec.push_back({key, &value}); + } + + std::sort(vec.begin(), vec.end(), [](const auto& l, const auto& r) { + if (l.first.Key < r.first.Key) { + return true; + } + if (l.first.Value < r.first.Value) { + return true; + } + return false; + }); + + for (const auto& [key, value] : vec) { + out << TString(offset, ' ') << "\"" << key.Key << "/" << key.Value << "\"" << Endl; + Print(*value, out, offset + 4); + } + } + + TString TEntitiesIndex::PrintDebugString() const { + TStringStream res; + res << Endl; + + Print(Idx_, res); + + return res.Str(); + } +} diff --git a/library/cpp/tvmauth/client/misc/roles/entities_index.h b/library/cpp/tvmauth/client/misc/roles/entities_index.h new file mode 100644 index 0000000000..bf42750d52 --- /dev/null +++ b/library/cpp/tvmauth/client/misc/roles/entities_index.h @@ -0,0 +1,107 @@ +#pragma once + +#include "types.h" + +#include <library/cpp/tvmauth/client/exception.h> + +#include <set> +#include <vector> + +namespace NTvmAuth::NRoles { + class TEntitiesIndex: TMoveOnly { + public: + struct TSubTree; + using TIdxByAttrs = THashMap<TKeyValue, TSubTree>; + + struct TSubTree { + std::vector<TEntityPtr> Entities; + TIdxByAttrs SubTree; + }; + + class TStage { + public: + TStage(const std::set<TString>& k); + + bool GetNextKeySet(std::vector<TString>& out); + + private: + std::vector<TString> Keys_; + size_t Id_ = 0; + }; + + public: + TEntitiesIndex(const std::vector<TEntityPtr>& entities); + + /** + * Iterators must be to sorted unique key/value + */ + template <typename Iterator> + bool ContainsExactEntity(Iterator begin, Iterator end) const; + + /** + * Iterators must be to sorted unique key/value + */ + template <typename Iterator> + const std::vector<TEntityPtr>& GetEntitiesWithAttrs(Iterator begin, Iterator end) const; + + public: // for tests + static std::set<TString> GetUniqueSortedKeys(const std::vector<TEntityPtr>& entities); + static void MakeUnique(TEntitiesIndex::TSubTree& branch); + + TString PrintDebugString() const; + + private: + template <typename Iterator> + const TSubTree* FindSubtree(Iterator begin, Iterator end, size_t& size) const; + + private: + TSubTree Idx_; + std::vector<TEntityPtr> EmptyResult_; + }; + + template <typename Iterator> + bool TEntitiesIndex::ContainsExactEntity(Iterator begin, Iterator end) const { + size_t size = 0; + const TSubTree* subtree = FindSubtree(begin, end, size); + if (!subtree) { + return false; + } + + auto res = std::find_if( + subtree->Entities.begin(), + subtree->Entities.end(), + [size](const auto& e) { return size == e->size(); }); + return res != subtree->Entities.end(); + } + + template <typename Iterator> + const std::vector<TEntityPtr>& TEntitiesIndex::GetEntitiesWithAttrs(Iterator begin, Iterator end) const { + size_t size = 0; + const TSubTree* subtree = FindSubtree(begin, end, size); + if (!subtree) { + return EmptyResult_; + } + + return subtree->Entities; + } + + template <typename Iterator> + const TEntitiesIndex::TSubTree* TEntitiesIndex::FindSubtree(Iterator begin, + Iterator end, + size_t& size) const { + const TSubTree* subtree = &Idx_; + size = 0; + + for (auto attr = begin; attr != end; ++attr) { + auto it = subtree->SubTree.find(TKeyValueView{attr->first, attr->second}); + if (it == subtree->SubTree.end()) { + return nullptr; + } + + ++size; + subtree = &it->second; + } + + return subtree; + } +} diff --git a/library/cpp/tvmauth/client/misc/roles/parser.cpp b/library/cpp/tvmauth/client/misc/roles/parser.cpp new file mode 100644 index 0000000000..28faf4c057 --- /dev/null +++ b/library/cpp/tvmauth/client/misc/roles/parser.cpp @@ -0,0 +1,149 @@ +#include "parser.h" + +#include <library/cpp/json/json_reader.h> + +#include <util/string/cast.h> + +namespace NTvmAuth::NRoles { + static void GetRequiredValue(const NJson::TJsonValue& doc, + TStringBuf key, + NJson::TJsonValue& obj) { + Y_ENSURE(doc.GetValue(key, &obj), "Missing '" << key << "'"); + } + + static ui64 GetRequiredUInt(const NJson::TJsonValue& doc, + TStringBuf key) { + NJson::TJsonValue obj; + GetRequiredValue(doc, key, obj); + Y_ENSURE(obj.IsUInteger(), "key '" << key << "' must be uint"); + return obj.GetUInteger(); + } + + static bool GetOptionalMap(const NJson::TJsonValue& doc, + TStringBuf key, + NJson::TJsonValue& obj) { + if (!doc.GetValue(key, &obj)) { + return false; + } + + Y_ENSURE(obj.IsMap(), "'" << key << "' must be object"); + return true; + } + + TRolesPtr TParser::Parse(TRawPtr decodedBlob) { + try { + return ParseImpl(decodedBlob); + } catch (const std::exception& e) { + throw yexception() << "Failed to parse roles from tirole: " << e.what() + << ". '" << *decodedBlob << "'"; + } + } + + TRolesPtr TParser::ParseImpl(TRawPtr decodedBlob) { + NJson::TJsonValue doc; + Y_ENSURE(NJson::ReadJsonTree(*decodedBlob, &doc), "Invalid json"); + Y_ENSURE(doc.IsMap(), "Json must be object"); + + TRoles::TTvmConsumers tvm = GetConsumers<TTvmId>(doc, "tvm"); + TRoles::TUserConsumers user = GetConsumers<TUid>(doc, "user"); + + // fetch it last to provide more correct apply instant + TRoles::TMeta meta = GetMeta(doc); + + return std::make_shared<TRoles>( + std::move(meta), + std::move(tvm), + std::move(user), + std::move(decodedBlob)); + } + + TRoles::TMeta TParser::GetMeta(const NJson::TJsonValue& doc) { + TRoles::TMeta res; + + NJson::TJsonValue obj; + GetRequiredValue(doc, "revision", obj); + if (obj.IsString()) { + res.Revision = obj.GetString(); + } else if (obj.IsUInteger()) { + res.Revision = ToString(obj.GetUInteger()); + } else { + ythrow yexception() << "'revision' has unexpected type: " << obj.GetType(); + } + + res.BornTime = TInstant::Seconds(GetRequiredUInt(doc, "born_date")); + + return res; + } + + template <typename Id> + THashMap<Id, TConsumerRolesPtr> TParser::GetConsumers(const NJson::TJsonValue& doc, + TStringBuf type) { + THashMap<Id, TConsumerRolesPtr> res; + + NJson::TJsonValue obj; + if (!GetOptionalMap(doc, type, obj)) { + return res; + } + + for (const auto& [key, value] : obj.GetMap()) { + Y_ENSURE(value.IsMap(), + "roles for consumer must be map: '" << key << "' is " << value.GetType()); + + Id id = 0; + Y_ENSURE(TryIntFromString<10>(key, id), + "id must be valid positive number of proper size for " + << type << ". got '" + << key << "'"); + + Y_ENSURE(res.emplace(id, GetConsumer(value, key)).second, + "consumer duplicate detected: '" << key << "' for " << type); + } + + return res; + } + + TConsumerRolesPtr TParser::GetConsumer(const NJson::TJsonValue& obj, TStringBuf consumer) { + TEntitiesByRoles entities; + + for (const auto& [key, value] : obj.GetMap()) { + Y_ENSURE(value.IsArray(), + "entities for roles must be array: '" << key << "' is " << value.GetType()); + + entities.emplace(key, GetEntities(value, consumer, key)); + } + + return std::make_shared<TConsumerRoles>(std::move(entities)); + } + + TEntitiesPtr TParser::GetEntities(const NJson::TJsonValue& obj, + TStringBuf consumer, + TStringBuf role) { + std::vector<TEntityPtr> entities; + entities.reserve(obj.GetArray().size()); + + for (const NJson::TJsonValue& e : obj.GetArray()) { + Y_ENSURE(e.IsMap(), + "role entity for role must be map: consumer '" + << consumer << "' with role '" << role << "' has " << e.GetType()); + + entities.push_back(GetEntity(e, consumer, role)); + } + + return std::make_shared<TEntities>(TEntities(entities)); + } + + TEntityPtr TParser::GetEntity(const NJson::TJsonValue& obj, TStringBuf consumer, TStringBuf role) { + TEntityPtr res = std::make_shared<TEntity>(); + + for (const auto& [key, value] : obj.GetMap()) { + Y_ENSURE(value.IsString(), + "entity is map (str->str), got value " + << value.GetType() << ". consumer '" + << consumer << "' with role '" << role << "'"); + + res->emplace(key, value.GetString()); + } + + return res; + } +} diff --git a/library/cpp/tvmauth/client/misc/roles/parser.h b/library/cpp/tvmauth/client/misc/roles/parser.h new file mode 100644 index 0000000000..0982ba78c6 --- /dev/null +++ b/library/cpp/tvmauth/client/misc/roles/parser.h @@ -0,0 +1,36 @@ +#pragma once + +#include "roles.h" +#include "types.h" + +namespace NJson { + class TJsonValue; +} + +namespace NTvmAuth::NRoles { + class TParser { + public: + static TRolesPtr Parse(TRawPtr decodedBlob); + + public: + static TRolesPtr ParseImpl(TRawPtr decodedBlob); + static TRoles::TMeta GetMeta(const NJson::TJsonValue& doc); + + template <typename Id> + static THashMap<Id, TConsumerRolesPtr> GetConsumers( + const NJson::TJsonValue& doc, + TStringBuf key); + + static TConsumerRolesPtr GetConsumer( + const NJson::TJsonValue& obj, + TStringBuf consumer); + static TEntitiesPtr GetEntities( + const NJson::TJsonValue& obj, + TStringBuf consumer, + TStringBuf role); + static TEntityPtr GetEntity( + const NJson::TJsonValue& obj, + TStringBuf consumer, + TStringBuf role); + }; +} diff --git a/library/cpp/tvmauth/client/misc/roles/roles.cpp b/library/cpp/tvmauth/client/misc/roles/roles.cpp new file mode 100644 index 0000000000..0761033104 --- /dev/null +++ b/library/cpp/tvmauth/client/misc/roles/roles.cpp @@ -0,0 +1,101 @@ +#include "roles.h" + +#include <library/cpp/tvmauth/checked_service_ticket.h> +#include <library/cpp/tvmauth/checked_user_ticket.h> + +namespace NTvmAuth::NRoles { + TRoles::TRoles(TMeta&& meta, + TTvmConsumers tvm, + TUserConsumers user, + TRawPtr raw) + : Meta_(std::move(meta)) + , TvmIds_(std::move(tvm)) + , Users_(std::move(user)) + , Raw_(std::move(raw)) + { + Y_ENSURE(Raw_); + } + + TConsumerRolesPtr TRoles::GetRolesForService(const TCheckedServiceTicket& t) const { + Y_ENSURE_EX(t, + TIllegalUsage() << "Service ticket must be valid, got: " << t.GetStatus()); + auto it = TvmIds_.find(t.GetSrc()); + return it == TvmIds_.end() ? TConsumerRolesPtr() : it->second; + } + + TConsumerRolesPtr TRoles::GetRolesForUser(const TCheckedUserTicket& t, + std::optional<TUid> selectedUid) const { + Y_ENSURE_EX(t, + TIllegalUsage() << "User ticket must be valid, got: " << t.GetStatus()); + Y_ENSURE_EX(t.GetEnv() == EBlackboxEnv::ProdYateam, + TIllegalUsage() << "User ticket must be from ProdYateam, got from " << t.GetEnv()); + + TUid uid = t.GetDefaultUid(); + if (selectedUid) { + auto it = std::find(t.GetUids().begin(), t.GetUids().end(), *selectedUid); + Y_ENSURE_EX(it != t.GetUids().end(), + TIllegalUsage() << "selectedUid must be in user ticket but it's not: " + << *selectedUid); + uid = *selectedUid; + } + + auto it = Users_.find(uid); + return it == Users_.end() ? TConsumerRolesPtr() : it->second; + } + + const TRoles::TMeta& TRoles::GetMeta() const { + return Meta_; + } + + const TString& TRoles::GetRaw() const { + return *Raw_; + } + + bool TRoles::CheckServiceRole(const TCheckedServiceTicket& t, + const TStringBuf roleName) const { + TConsumerRolesPtr c = GetRolesForService(t); + return c ? c->HasRole(roleName) : false; + } + + bool TRoles::CheckUserRole(const TCheckedUserTicket& t, + const TStringBuf roleName, + std::optional<TUid> selectedUid) const { + TConsumerRolesPtr c = GetRolesForUser(t, selectedUid); + return c ? c->HasRole(roleName) : false; + } + + bool TRoles::CheckServiceRoleForExactEntity(const TCheckedServiceTicket& t, + const TStringBuf roleName, + const TEntity& exactEntity) const { + TConsumerRolesPtr c = GetRolesForService(t); + return c ? c->CheckRoleForExactEntity(roleName, exactEntity) : false; + } + + bool TRoles::CheckUserRoleForExactEntity(const TCheckedUserTicket& t, + const TStringBuf roleName, + const TEntity& exactEntity, + std::optional<TUid> selectedUid) const { + TConsumerRolesPtr c = GetRolesForUser(t, selectedUid); + return c ? c->CheckRoleForExactEntity(roleName, exactEntity) : false; + } + + TConsumerRoles::TConsumerRoles(TEntitiesByRoles roles) + : Roles_(std::move(roles)) + { + } + + bool TConsumerRoles::CheckRoleForExactEntity(const TStringBuf roleName, + const TEntity& exactEntity) const { + auto it = Roles_.find(roleName); + if (it == Roles_.end()) { + return false; + } + + return it->second->Contains(exactEntity); + } + + TEntities::TEntities(TEntitiesIndex idx) + : Idx_(std::move(idx)) + { + } +} diff --git a/library/cpp/tvmauth/client/misc/roles/roles.h b/library/cpp/tvmauth/client/misc/roles/roles.h new file mode 100644 index 0000000000..6d510ee8a1 --- /dev/null +++ b/library/cpp/tvmauth/client/misc/roles/roles.h @@ -0,0 +1,186 @@ +#pragma once + +#include "entities_index.h" +#include "types.h" + +#include <library/cpp/tvmauth/client/exception.h> + +#include <library/cpp/tvmauth/type.h> + +#include <util/datetime/base.h> +#include <util/generic/array_ref.h> +#include <util/generic/hash.h> + +#include <vector> + +namespace NTvmAuth { + class TCheckedServiceTicket; + class TCheckedUserTicket; +} + +namespace NTvmAuth::NRoles { + class TRoles { + public: + struct TMeta { + TString Revision; + TInstant BornTime; + TInstant Applied = TInstant::Now(); + }; + + using TTvmConsumers = THashMap<TTvmId, TConsumerRolesPtr>; + using TUserConsumers = THashMap<TUid, TConsumerRolesPtr>; + + TRoles(TMeta&& meta, + TTvmConsumers tvm, + TUserConsumers user, + TRawPtr raw); + + /** + * @return ptr to roles. It will be nullptr if there are no roles + */ + TConsumerRolesPtr GetRolesForService(const TCheckedServiceTicket& t) const; + + /** + * @return ptr to roles. It will be nullptr if there are no roles + */ + TConsumerRolesPtr GetRolesForUser(const TCheckedUserTicket& t, + std::optional<TUid> selectedUid = {}) const; + + const TMeta& GetMeta() const; + const TString& GetRaw() const; + + public: // shortcuts + /** + * @brief CheckServiceRole() is shortcut for simple role checking - for any possible entity + */ + bool CheckServiceRole( + const TCheckedServiceTicket& t, + const TStringBuf roleName) const; + + /** + * @brief CheckUserRole() is shortcut for simple role checking - for any possible entity + */ + bool CheckUserRole( + const TCheckedUserTicket& t, + const TStringBuf roleName, + std::optional<TUid> selectedUid = {}) const; + + /** + * @brief CheckServiceRoleForExactEntity() is shortcut for simple role checking for exact entity + */ + bool CheckServiceRoleForExactEntity( + const TCheckedServiceTicket& t, + const TStringBuf roleName, + const TEntity& exactEntity) const; + + /** + * @brief CheckUserRoleForExactEntity() is shortcut for simple role checking for exact entity + */ + bool CheckUserRoleForExactEntity( + const TCheckedUserTicket& t, + const TStringBuf roleName, + const TEntity& exactEntity, + std::optional<TUid> selectedUid = {}) const; + + private: + TMeta Meta_; + TTvmConsumers TvmIds_; + TUserConsumers Users_; + TRawPtr Raw_; + }; + + class TConsumerRoles { + public: + TConsumerRoles(TEntitiesByRoles roles); + + bool HasRole(const TStringBuf roleName) const { + return Roles_.contains(roleName); + } + + const TEntitiesByRoles& GetRoles() const { + return Roles_; + } + + /** + * @return ptr to entries. It will be nullptr if there is no role + */ + TEntitiesPtr GetEntitiesForRole(const TStringBuf roleName) const { + auto it = Roles_.find(roleName); + return it == Roles_.end() ? TEntitiesPtr() : it->second; + } + + /** + * @brief CheckRoleForExactEntity() is shortcut for simple role checking for exact entity + */ + bool CheckRoleForExactEntity(const TStringBuf roleName, + const TEntity& exactEntity) const; + + private: + TEntitiesByRoles Roles_; + }; + + class TEntities { + public: + TEntities(TEntitiesIndex idx); + + /** + * @brief Contains() provides info about entity presence + */ + bool Contains(const TEntity& exactEntity) const { + return Idx_.ContainsExactEntity(exactEntity.begin(), exactEntity.end()); + } + + /** + * @brief The same as Contains() + * It checks span for sorted and unique properties. + */ + template <class StrKey = TString, class StrValue = TString> + bool ContainsSortedUnique( + const TArrayRef<const std::pair<StrKey, StrValue>>& exactEntity) const { + CheckSpan(exactEntity); + return Idx_.ContainsExactEntity(exactEntity.begin(), exactEntity.end()); + } + + /** + * @brief GetEntitiesWithAttrs() collects entities with ALL attributes from `attrs` + */ + template <class StrKey = TString, class StrValue = TString> + const std::vector<TEntityPtr>& GetEntitiesWithAttrs( + const std::map<StrKey, StrValue>& attrs) const { + return Idx_.GetEntitiesWithAttrs(attrs.begin(), attrs.end()); + } + + /** + * @brief The same as GetEntitiesWithAttrs() + * It checks span for sorted and unique properties. + */ + template <class StrKey = TString, class StrValue = TString> + const std::vector<TEntityPtr>& GetEntitiesWithSortedUniqueAttrs( + const TArrayRef<const std::pair<StrKey, StrValue>>& attrs) const { + CheckSpan(attrs); + return Idx_.GetEntitiesWithAttrs(attrs.begin(), attrs.end()); + } + + private: + template <class StrKey, class StrValue> + static void CheckSpan(const TArrayRef<const std::pair<StrKey, StrValue>>& attrs) { + if (attrs.empty()) { + return; + } + + auto prev = attrs.begin(); + for (auto it = prev + 1; it != attrs.end(); ++it) { + Y_ENSURE_EX(prev->first != it->first, + TIllegalUsage() << "attrs are not unique: '" << it->first << "'"); + Y_ENSURE_EX(prev->first < it->first, + TIllegalUsage() << "attrs are not sorted: '" << prev->first + << "' before '" << it->first << "'"); + + prev = it; + } + } + + private: + TEntitiesIndex Idx_; + }; +} diff --git a/library/cpp/tvmauth/client/misc/roles/types.h b/library/cpp/tvmauth/client/misc/roles/types.h new file mode 100644 index 0000000000..de0745e72e --- /dev/null +++ b/library/cpp/tvmauth/client/misc/roles/types.h @@ -0,0 +1,70 @@ +#pragma once + +#include <util/generic/hash_set.h> + +#include <map> +#include <memory> + +namespace NTvmAuth::NRoles { + using TEntity = std::map<TString, TString>; + using TEntityPtr = std::shared_ptr<TEntity>; + + class TEntities; + using TEntitiesPtr = std::shared_ptr<TEntities>; + + using TEntitiesByRoles = THashMap<TString, TEntitiesPtr>; + + class TConsumerRoles; + using TConsumerRolesPtr = std::shared_ptr<TConsumerRoles>; + + class TRoles; + using TRolesPtr = std::shared_ptr<TRoles>; + + using TRawPtr = std::shared_ptr<TString>; + + template <class T> + struct TKeyValueBase { + T Key; + T Value; + + template <typename U> + bool operator==(const TKeyValueBase<U>& o) const { + return Key == o.Key && Value == o.Value; + } + }; + + using TKeyValue = TKeyValueBase<TString>; + using TKeyValueView = TKeyValueBase<TStringBuf>; +} + +// Traits + +template <> +struct THash<NTvmAuth::NRoles::TKeyValue> { + std::size_t operator()(const NTvmAuth::NRoles::TKeyValue& e) const { + return std::hash<std::string_view>()(e.Key) + std::hash<std::string_view>()(e.Value); + } + + std::size_t operator()(const NTvmAuth::NRoles::TKeyValueView& e) const { + return std::hash<std::string_view>()(e.Key) + std::hash<std::string_view>()(e.Value); + } +}; + +template <> +struct TEqualTo<NTvmAuth::NRoles::TKeyValue> { + using is_transparent = std::true_type; + + template <typename T, typename U> + bool operator()(const NTvmAuth::NRoles::TKeyValueBase<T>& l, + const NTvmAuth::NRoles::TKeyValueBase<U>& r) { + return l == r; + } +}; + +inline bool operator<(const NTvmAuth::NRoles::TEntityPtr& l, const NTvmAuth::NRoles::TEntityPtr& r) { + return *l < *r; +} + +inline bool operator==(const NTvmAuth::NRoles::TEntityPtr& l, const NTvmAuth::NRoles::TEntityPtr& r) { + return *l == *r; +} diff --git a/library/cpp/tvmauth/client/misc/service_tickets.h b/library/cpp/tvmauth/client/misc/service_tickets.h new file mode 100644 index 0000000000..6a24bd5689 --- /dev/null +++ b/library/cpp/tvmauth/client/misc/service_tickets.h @@ -0,0 +1,86 @@ +#pragma once + +#include "settings.h" +#include "roles/roles.h" + +#include <library/cpp/tvmauth/src/utils.h> + +#include <util/datetime/base.h> +#include <util/generic/hash.h> +#include <util/generic/maybe.h> +#include <util/generic/noncopyable.h> +#include <util/generic/ptr.h> + +namespace NTvmAuth::NInternal { + class TClientCaningKnife; +} + +namespace NTvmAuth { + class TServiceTickets: public TAtomicRefCount<TServiceTickets> { + public: + using TMapAliasStr = THashMap<TClientSettings::TAlias, TString>; + using TMapIdStr = THashMap<TTvmId, TString>; + using TIdSet = THashSet<TTvmId>; + using TAliasSet = THashSet<TClientSettings::TAlias>; + using TMapAliasId = THashMap<TClientSettings::TAlias, TTvmId>; + + TServiceTickets(TMapIdStr&& tickets, TMapIdStr&& errors, const TMapAliasId& dstMap) + : TicketsById(std::move(tickets)) + , ErrorsById(std::move(errors)) + { + InitAliasesAndUnfetchedIds(dstMap); + InitInvalidationTime(); + } + + static TInstant GetInvalidationTime(const TMapIdStr& ticketsById) { + TInstant res; + + for (const auto& pair : ticketsById) { + TMaybe<TInstant> t = NTvmAuth::NInternal::TCanningKnife::GetExpirationTime(pair.second); + if (!t) { + continue; + } + + res = res == TInstant() ? *t : std::min(res, *t); + } + + return res; + } + + public: + TMapIdStr TicketsById; + TMapIdStr ErrorsById; + TMapAliasStr TicketsByAlias; + TMapAliasStr ErrorsByAlias; + TInstant InvalidationTime; + TIdSet UnfetchedIds; + TAliasSet UnfetchedAliases; + + private: + void InitAliasesAndUnfetchedIds(const TMapAliasId& dstMap) { + for (const auto& pair : dstMap) { + auto it = TicketsById.find(pair.second); + auto errIt = ErrorsById.find(pair.second); + + if (it == TicketsById.end()) { + if (errIt != ErrorsById.end()) { + Y_ENSURE(ErrorsByAlias.insert({pair.first, errIt->second}).second, + "failed to add: " << pair.first); + } else { + UnfetchedAliases.insert(pair.first); + UnfetchedIds.insert(pair.second); + } + } else { + Y_ENSURE(TicketsByAlias.insert({pair.first, it->second}).second, + "failed to add: " << pair.first); + } + } + } + + void InitInvalidationTime() { + InvalidationTime = GetInvalidationTime(TicketsById); + } + }; + + using TServiceTicketsPtr = TIntrusiveConstPtr<TServiceTickets>; +} diff --git a/library/cpp/tvmauth/client/misc/settings.h b/library/cpp/tvmauth/client/misc/settings.h new file mode 100644 index 0000000000..8fae6c34d3 --- /dev/null +++ b/library/cpp/tvmauth/client/misc/settings.h @@ -0,0 +1,13 @@ +#pragma once + +#include <util/generic/fwd.h> + +namespace NTvmAuth { + class TClientSettings { + public: + /*! + * Look at description in relevant settings: NTvmApi::TClientSettings or NTvmTool::TClientSettings + */ + using TAlias = TString; + }; +} diff --git a/library/cpp/tvmauth/client/misc/src_checker.h b/library/cpp/tvmauth/client/misc/src_checker.h new file mode 100644 index 0000000000..bb99fe8884 --- /dev/null +++ b/library/cpp/tvmauth/client/misc/src_checker.h @@ -0,0 +1,31 @@ +#pragma once + +#include "roles/roles.h" + +#include <library/cpp/tvmauth/client/exception.h> + +#include <library/cpp/tvmauth/checked_service_ticket.h> +#include <library/cpp/tvmauth/src/service_impl.h> +#include <library/cpp/tvmauth/src/utils.h> + +namespace NTvmAuth { + class TSrcChecker { + public: + /*! + * Checking must be enabled in TClientSettings + * Can throw exception if cache is out of date or wrong config + * @param ticket + */ + static TCheckedServiceTicket Check(TCheckedServiceTicket ticket, NRoles::TRolesPtr r) { + Y_ENSURE_EX(r, TBrokenTvmClientSettings() << "Need to use TClientSettings::EnableRolesFetching()"); + NRoles::TConsumerRolesPtr roles = r->GetRolesForService(ticket); + if (roles) { + return ticket; + } + + TServiceTicketImplPtr impl = THolder(NInternal::TCanningKnife::GetS(ticket)); + impl->SetStatus(ETicketStatus::NoRoles); + return TCheckedServiceTicket(std::move(impl)); + } + }; +} diff --git a/library/cpp/tvmauth/client/misc/threaded_updater.cpp b/library/cpp/tvmauth/client/misc/threaded_updater.cpp new file mode 100644 index 0000000000..5d21ce67a7 --- /dev/null +++ b/library/cpp/tvmauth/client/misc/threaded_updater.cpp @@ -0,0 +1,111 @@ +#include "threaded_updater.h" + +#include <library/cpp/tvmauth/client/exception.h> + +#include <util/string/builder.h> +#include <util/system/spin_wait.h> +#include <util/system/thread.h> + +namespace NTvmAuth { + TThreadedUpdaterBase::TThreadedUpdaterBase(TDuration workerAwakingPeriod, + TLoggerPtr logger, + const TString& url, + ui16 port, + TDuration socketTimeout, + TDuration connectTimeout) + : WorkerAwakingPeriod_(workerAwakingPeriod) + , Logger_(std::move(logger)) + , TvmUrl_(url) + , TvmPort_(port) + , TvmSocketTimeout_(socketTimeout) + , TvmConnectTimeout_(connectTimeout) + , IsStopped_(true) + { + Y_ENSURE_EX(Logger_, TNonRetriableException() << "Logger is required"); + + ServiceTicketsDurations_.RefreshPeriod = TDuration::Hours(1); + ServiceTicketsDurations_.Expiring = TDuration::Hours(2); + ServiceTicketsDurations_.Invalid = TDuration::Hours(11); + + PublicKeysDurations_.RefreshPeriod = TDuration::Days(1); + PublicKeysDurations_.Expiring = TDuration::Days(2); + PublicKeysDurations_.Invalid = TDuration::Days(6); + } + + TThreadedUpdaterBase::~TThreadedUpdaterBase() { + StopWorker(); + } + + void TThreadedUpdaterBase::StartWorker() { + if (HttpClient_) { + HttpClient_->ResetConnection(); + } + Thread_ = MakeHolder<TThread>(WorkerWrap, this); + Thread_->Start(); + Started_.Wait(); + IsStopped_ = false; + } + + void TThreadedUpdaterBase::StopWorker() { + Event_.Signal(); + if (Thread_) { + Thread_.Reset(); + } + } + + TKeepAliveHttpClient& TThreadedUpdaterBase::GetClient() const { + if (!HttpClient_) { + HttpClient_ = MakeHolder<TKeepAliveHttpClient>(TvmUrl_, TvmPort_, TvmSocketTimeout_, TvmConnectTimeout_); + } + + return *HttpClient_; + } + + void TThreadedUpdaterBase::LogDebug(const TString& msg) const { + if (Logger_) { + Logger_->Debug(msg); + } + } + + void TThreadedUpdaterBase::LogInfo(const TString& msg) const { + if (Logger_) { + Logger_->Info(msg); + } + } + + void TThreadedUpdaterBase::LogWarning(const TString& msg) const { + if (Logger_) { + Logger_->Warning(msg); + } + } + + void TThreadedUpdaterBase::LogError(const TString& msg) const { + if (Logger_) { + Logger_->Error(msg); + } + } + + void* TThreadedUpdaterBase::WorkerWrap(void* arg) { + TThread::SetCurrentThreadName("TicketParserUpd"); + TThreadedUpdaterBase& this_ = *reinterpret_cast<TThreadedUpdaterBase*>(arg); + this_.Started_.Signal(); + this_.LogDebug("Thread-worker started"); + + while (true) { + if (this_.Event_.WaitT(this_.WorkerAwakingPeriod_)) { + break; + } + + try { + this_.Worker(); + this_.GetClient().ResetConnection(); + } catch (const std::exception& e) { // impossible now + this_.LogError(TStringBuilder() << "Failed to generate new cache: " << e.what()); + } + } + + this_.LogDebug("Thread-worker stopped"); + this_.IsStopped_ = true; + return nullptr; + } +} diff --git a/library/cpp/tvmauth/client/misc/threaded_updater.h b/library/cpp/tvmauth/client/misc/threaded_updater.h new file mode 100644 index 0000000000..783684ba3b --- /dev/null +++ b/library/cpp/tvmauth/client/misc/threaded_updater.h @@ -0,0 +1,76 @@ +#pragma once + +#include "async_updater.h" +#include "settings.h" + +#include <library/cpp/tvmauth/client/logger.h> + +#include <library/cpp/http/simple/http_client.h> + +#include <util/datetime/base.h> +#include <util/generic/ptr.h> +#include <util/system/event.h> +#include <util/system/thread.h> + +class TKeepAliveHttpClient; + +namespace NTvmAuth::NInternal { + class TClientCaningKnife; +} +namespace NTvmAuth { + class TThreadedUpdaterBase: public TAsyncUpdaterBase { + public: + TThreadedUpdaterBase(TDuration workerAwakingPeriod, + TLoggerPtr logger, + const TString& url, + ui16 port, + TDuration socketTimeout, + TDuration connectTimeout); + virtual ~TThreadedUpdaterBase(); + + protected: + void StartWorker(); + void StopWorker(); + + virtual void Worker() { + } + + TKeepAliveHttpClient& GetClient() const; + + void LogDebug(const TString& msg) const; + void LogInfo(const TString& msg) const; + void LogWarning(const TString& msg) const; + void LogError(const TString& msg) const; + + protected: + TDuration WorkerAwakingPeriod_; + + const TLoggerPtr Logger_; + + protected: + const TString TvmUrl_; + + private: + static void* WorkerWrap(void* arg); + + void StartTvmClientStopping() const override { + Event_.Signal(); + } + + bool IsTvmClientStopped() const override { + return IsStopped_; + } + + private: + mutable THolder<TKeepAliveHttpClient> HttpClient_; + + const ui32 TvmPort_; + const TDuration TvmSocketTimeout_; + const TDuration TvmConnectTimeout_; + + mutable TAutoEvent Event_; + mutable TAutoEvent Started_; + std::atomic_bool IsStopped_; + THolder<TThread> Thread_; + }; +} diff --git a/library/cpp/tvmauth/client/misc/tool/meta_info.cpp b/library/cpp/tvmauth/client/misc/tool/meta_info.cpp new file mode 100644 index 0000000000..9a0ae228fe --- /dev/null +++ b/library/cpp/tvmauth/client/misc/tool/meta_info.cpp @@ -0,0 +1,208 @@ +#include "meta_info.h" + +#include <library/cpp/json/json_reader.h> + +#include <util/string/builder.h> + +namespace NTvmAuth::NTvmTool { + TString TMetaInfo::TConfig::ToString() const { + TStringStream s; + s << "self_tvm_id=" << SelfTvmId << ", " + << "bb_env=" << BbEnv << ", " + << "idm_slug=" << (IdmSlug ? IdmSlug : "<NULL>") << ", " + << "dsts=["; + + for (const auto& pair : DstAliases) { + s << "(" << pair.first << ":" << pair.second << ")"; + } + + s << "]"; + + return std::move(s.Str()); + } + + TMetaInfo::TMetaInfo(TLoggerPtr logger) + : Logger_(std::move(logger)) + { + } + + TMetaInfo::TConfigPtr TMetaInfo::Init(TKeepAliveHttpClient& client, + const TClientSettings& settings) { + ApplySettings(settings); + + TryPing(client); + const TString metaString = Fetch(client); + if (Logger_) { + TStringStream s; + s << "Meta info fetched from " << settings.GetHostname() << ":" << settings.GetPort(); + Logger_->Debug(s.Str()); + } + + try { + Config_.Set(ParseMetaString(metaString, SelfAlias_)); + } catch (const yexception& e) { + ythrow TNonRetriableException() << "Malformed json from tvmtool: " << e.what(); + } + TConfigPtr cfg = Config_.Get(); + Y_ENSURE_EX(cfg, TNonRetriableException() << "Alias '" << SelfAlias_ << "' not found in meta info"); + + if (Logger_) { + Logger_->Info("Meta: " + cfg->ToString()); + } + + return cfg; + } + + TString TMetaInfo::GetRequestForTickets(const TConfig& config) { + Y_ENSURE(!config.DstAliases.empty()); + + TStringStream s; + s << "/tvm/tickets" + << "?src=" << config.SelfTvmId + << "&dsts="; + + for (const auto& pair : config.DstAliases) { + s << pair.second << ","; // avoid aliases - url-encoding required + } + s.Str().pop_back(); + + return s.Str(); + } + + bool TMetaInfo::TryUpdateConfig(TKeepAliveHttpClient& client) { + const TString metaString = Fetch(client); + + TConfigPtr config; + try { + config = ParseMetaString(metaString, SelfAlias_); + } catch (const yexception& e) { + ythrow TNonRetriableException() << "Malformed json from tvmtool: " << e.what(); + } + Y_ENSURE_EX(config, TNonRetriableException() << "Alias '" << SelfAlias_ << "' not found in meta info"); + + TConfigPtr oldConfig = Config_.Get(); + if (*config == *oldConfig) { + return false; + } + + if (Logger_) { + Logger_->Info(TStringBuilder() + << "Meta was updated. Old: (" << oldConfig->ToString() + << "). New: (" << config->ToString() << ")"); + } + + Config_ = config; + return true; + } + + void TMetaInfo::TryPing(TKeepAliveHttpClient& client) { + try { + TStringStream s; + TKeepAliveHttpClient::THttpCode code = client.DoGet("/tvm/ping", &s); + if (code < 200 || 300 <= code) { + throw yexception() << "(" << code << ") " << s.Str(); + } + } catch (const std::exception& e) { + ythrow TNonRetriableException() << "Failed to connect to tvmtool: " << e.what(); + } + } + + TString TMetaInfo::Fetch(TKeepAliveHttpClient& client) const { + TStringStream res; + TKeepAliveHttpClient::THttpCode code; + try { + code = client.DoGet("/tvm/private_api/__meta__", &res, AuthHeader_); + } catch (const std::exception& e) { + ythrow TRetriableException() << "Failed to fetch meta data from tvmtool: " << e.what(); + } + + if (code != 200) { + Y_ENSURE_EX(code != 404, + TNonRetriableException() << "Library does not support so old tvmtool. You need tvmtool>=1.1.0"); + + TStringStream err; + err << "Failed to fetch meta from tvmtool: " << client.GetHost() << ":" << client.GetPort() + << " (" << code << "): " << res.Str(); + Y_ENSURE_EX(!(500 <= code && code < 600), TRetriableException() << err.Str()); + ythrow TNonRetriableException() << err.Str(); + } + + return res.Str(); + } + + static TMetaInfo::TDstAliases::value_type ParsePair(const NJson::TJsonValue& val, const TString& meta) { + NJson::TJsonValue jAlias; + Y_ENSURE(val.GetValue("alias", &jAlias), meta); + Y_ENSURE(jAlias.IsString(), meta); + + NJson::TJsonValue jClientId; + Y_ENSURE(val.GetValue("client_id", &jClientId), meta); + Y_ENSURE(jClientId.IsInteger(), meta); + + return {jAlias.GetString(), jClientId.GetInteger()}; + } + + TMetaInfo::TConfigPtr TMetaInfo::ParseMetaString(const TString& meta, const TString& self) { + NJson::TJsonValue jDoc; + Y_ENSURE(NJson::ReadJsonTree(meta, &jDoc), meta); + + NJson::TJsonValue jEnv; + Y_ENSURE(jDoc.GetValue("bb_env", &jEnv), meta); + + NJson::TJsonValue jTenants; + Y_ENSURE(jDoc.GetValue("tenants", &jTenants), meta); + Y_ENSURE(jTenants.IsArray(), meta); + + for (const NJson::TJsonValue& jTen : jTenants.GetArray()) { + NJson::TJsonValue jSelf; + Y_ENSURE(jTen.GetValue("self", &jSelf), meta); + auto selfPair = ParsePair(jSelf, meta); + if (selfPair.first != self) { + continue; + } + + TConfigPtr config = std::make_shared<TConfig>(); + config->SelfTvmId = selfPair.second; + config->BbEnv = BbEnvFromString(jEnv.GetString(), meta); + + { + NJson::TJsonValue jSlug; + if (jTen.GetValue("idm_slug", &jSlug)) { + config->IdmSlug = jSlug.GetString(); + } + } + + NJson::TJsonValue jDsts; + Y_ENSURE(jTen.GetValue("dsts", &jDsts), meta); + Y_ENSURE(jDsts.IsArray(), meta); + for (const NJson::TJsonValue& jDst : jDsts.GetArray()) { + config->DstAliases.insert(ParsePair(jDst, meta)); + } + + return config; + } + + return {}; + } + + void TMetaInfo::ApplySettings(const TClientSettings& settings) { + AuthHeader_ = {{"Authorization", settings.GetAuthToken()}}; + SelfAlias_ = settings.GetSelfAlias(); + } + + EBlackboxEnv TMetaInfo::BbEnvFromString(const TString& env, const TString& meta) { + if (env == "Prod") { + return EBlackboxEnv::Prod; + } else if (env == "Test") { + return EBlackboxEnv::Test; + } else if (env == "ProdYaTeam") { + return EBlackboxEnv::ProdYateam; + } else if (env == "TestYaTeam") { + return EBlackboxEnv::TestYateam; + } else if (env == "Stress") { + return EBlackboxEnv::Stress; + } + + ythrow yexception() << "'bb_env'=='" << env << "'. " << meta; + } +} diff --git a/library/cpp/tvmauth/client/misc/tool/meta_info.h b/library/cpp/tvmauth/client/misc/tool/meta_info.h new file mode 100644 index 0000000000..9dd4f0dbf8 --- /dev/null +++ b/library/cpp/tvmauth/client/misc/tool/meta_info.h @@ -0,0 +1,69 @@ +#pragma once + +#include "settings.h" + +#include <library/cpp/tvmauth/client/misc/utils.h> + +#include <library/cpp/tvmauth/client/logger.h> + +#include <library/cpp/http/simple/http_client.h> + +namespace NTvmAuth::NTvmTool { + class TMetaInfo { + public: + using TDstAliases = THashMap<TClientSettings::TAlias, TTvmId>; + + struct TConfig { + TTvmId SelfTvmId = 0; + EBlackboxEnv BbEnv = EBlackboxEnv::Prod; + TString IdmSlug; + TDstAliases DstAliases; + + bool AreTicketsRequired() const { + return !DstAliases.empty(); + } + + TString ToString() const; + + bool operator==(const TConfig& c) const { + return SelfTvmId == c.SelfTvmId && + BbEnv == c.BbEnv && + IdmSlug == c.IdmSlug && + DstAliases == c.DstAliases; + } + }; + using TConfigPtr = std::shared_ptr<TConfig>; + + public: + TMetaInfo(TLoggerPtr logger); + + TConfigPtr Init(TKeepAliveHttpClient& client, + const TClientSettings& settings); + + static TString GetRequestForTickets(const TMetaInfo::TConfig& config); + + const TKeepAliveHttpClient::THeaders& GetAuthHeader() const { + return AuthHeader_; + } + + TConfigPtr GetConfig() const { + return Config_.Get(); + } + + bool TryUpdateConfig(TKeepAliveHttpClient& client); + + protected: + void TryPing(TKeepAliveHttpClient& client); + TString Fetch(TKeepAliveHttpClient& client) const; + static TConfigPtr ParseMetaString(const TString& meta, const TString& self); + void ApplySettings(const TClientSettings& settings); + static EBlackboxEnv BbEnvFromString(const TString& env, const TString& meta); + + protected: + NUtils::TProtectedValue<TConfigPtr> Config_; + TKeepAliveHttpClient::THeaders AuthHeader_; + + TLoggerPtr Logger_; + TString SelfAlias_; + }; +} diff --git a/library/cpp/tvmauth/client/misc/tool/roles_fetcher.cpp b/library/cpp/tvmauth/client/misc/tool/roles_fetcher.cpp new file mode 100644 index 0000000000..05b0856edc --- /dev/null +++ b/library/cpp/tvmauth/client/misc/tool/roles_fetcher.cpp @@ -0,0 +1,81 @@ +#include "roles_fetcher.h" + +#include <library/cpp/tvmauth/client/misc/roles/parser.h> + +#include <library/cpp/http/misc/httpcodes.h> +#include <library/cpp/string_utils/quote/quote.h> + +#include <util/string/builder.h> +#include <util/string/join.h> + +namespace NTvmAuth::NTvmTool { + TRolesFetcher::TRolesFetcher(const TRolesFetcherSettings& settings, TLoggerPtr logger) + : Settings_(settings) + , Logger_(std::move(logger)) + { + } + + bool TRolesFetcher::IsTimeToUpdate(TDuration sinceUpdate) const { + return Settings_.UpdatePeriod < sinceUpdate; + } + + bool TRolesFetcher::ShouldWarn(TDuration sinceUpdate) const { + return Settings_.WarnPeriod < sinceUpdate; + } + + bool TRolesFetcher::AreRolesOk() const { + return bool(GetCurrentRoles()); + } + + NUtils::TFetchResult TRolesFetcher::FetchActualRoles(const TKeepAliveHttpClient::THeaders& authHeader, + TKeepAliveHttpClient& client) const { + const TRequest req = CreateRequest(authHeader); + + TStringStream out; + THttpHeaders outHeaders; + + TKeepAliveHttpClient::THttpCode code = client.DoGet( + req.Url, + &out, + req.Headers, + &outHeaders); + + return {code, std::move(outHeaders), "/v2/roles", out.Str(), {}}; + } + + void TRolesFetcher::Update(NUtils::TFetchResult&& fetchResult) { + if (fetchResult.Code == HTTP_NOT_MODIFIED) { + Y_ENSURE(CurrentRoles_.Get(), + "tvmtool did not return any roles because current roles are actual," + " but there are no roles in memory - this should never happen"); + return; + } + + Y_ENSURE(fetchResult.Code == HTTP_OK, + "Unexpected code from tvmtool: " << fetchResult.Code << ". " << fetchResult.Response); + + CurrentRoles_.Set(NRoles::TParser::Parse(std::make_shared<TString>(std::move(fetchResult.Response)))); + + Logger_->Debug( + TStringBuilder() << "Succeed to update roles with revision " + << CurrentRoles_.Get()->GetMeta().Revision); + } + + NTvmAuth::NRoles::TRolesPtr TRolesFetcher::GetCurrentRoles() const { + return CurrentRoles_.Get(); + } + + TRolesFetcher::TRequest TRolesFetcher::CreateRequest(const TKeepAliveHttpClient::THeaders& authHeader) const { + TRequest request{ + .Url = "/v2/roles?self=" + CGIEscapeRet(Settings_.SelfAlias), + .Headers = authHeader, + }; + + NRoles::TRolesPtr roles = CurrentRoles_.Get(); + if (roles) { + request.Headers.emplace(IfNoneMatch_, Join("", "\"", roles->GetMeta().Revision, "\"")); + } + + return request; + } +} diff --git a/library/cpp/tvmauth/client/misc/tool/roles_fetcher.h b/library/cpp/tvmauth/client/misc/tool/roles_fetcher.h new file mode 100644 index 0000000000..8c60b59610 --- /dev/null +++ b/library/cpp/tvmauth/client/misc/tool/roles_fetcher.h @@ -0,0 +1,49 @@ +#pragma once + +#include <library/cpp/tvmauth/client/misc/fetch_result.h> +#include <library/cpp/tvmauth/client/misc/utils.h> +#include <library/cpp/tvmauth/client/misc/roles/roles.h> + +#include <library/cpp/tvmauth/client/logger.h> + +#include <util/datetime/base.h> +#include <util/generic/string.h> + +namespace NTvmAuth::NTvmTool { + struct TRolesFetcherSettings { + TString SelfAlias; + TDuration UpdatePeriod = TDuration::Minutes(1); + TDuration WarnPeriod = TDuration::Minutes(20); + }; + + class TRolesFetcher { + public: + TRolesFetcher(const TRolesFetcherSettings& settings, TLoggerPtr logger); + + bool IsTimeToUpdate(TDuration sinceUpdate) const; + bool ShouldWarn(TDuration sinceUpdate) const; + bool AreRolesOk() const; + + NUtils::TFetchResult FetchActualRoles(const TKeepAliveHttpClient::THeaders& authHeader, + TKeepAliveHttpClient& client) const; + void Update(NUtils::TFetchResult&& fetchResult); + + NTvmAuth::NRoles::TRolesPtr GetCurrentRoles() const; + + protected: + struct TRequest { + TString Url; + TKeepAliveHttpClient::THeaders Headers; + }; + + protected: + TRequest CreateRequest(const TKeepAliveHttpClient::THeaders& authHeader) const; + + private: + const TRolesFetcherSettings Settings_; + const TLoggerPtr Logger_; + const TString IfNoneMatch_ = "If-None-Match"; + + NUtils::TProtectedValue<NTvmAuth::NRoles::TRolesPtr> CurrentRoles_; + }; +} diff --git a/library/cpp/tvmauth/client/misc/tool/settings.cpp b/library/cpp/tvmauth/client/misc/tool/settings.cpp new file mode 100644 index 0000000000..894501f19d --- /dev/null +++ b/library/cpp/tvmauth/client/misc/tool/settings.cpp @@ -0,0 +1,37 @@ +#include "settings.h" + +#include <library/cpp/string_utils/url/url.h> + +#include <util/system/env.h> + +namespace NTvmAuth::NTvmTool { + TClientSettings::TClientSettings(const TAlias& selfAias) + : SelfAias_(selfAias) + , Hostname_("localhost") + , Port_(1) + , SocketTimeout_(TDuration::Seconds(5)) + , ConnectTimeout_(TDuration::Seconds(30)) + { + AuthToken_ = GetEnv("TVMTOOL_LOCAL_AUTHTOKEN"); + if (!AuthToken_) { + AuthToken_ = GetEnv("QLOUD_TVM_TOKEN"); + } + TStringBuf auth(AuthToken_); + FixSpaces(auth); + AuthToken_ = auth; + + const TString url = GetEnv("DEPLOY_TVM_TOOL_URL"); + if (url) { + TStringBuf scheme, host; + TryGetSchemeHostAndPort(url, scheme, host, Port_); + } + + Y_ENSURE_EX(SelfAias_, TBrokenTvmClientSettings() << "Alias for your TVM client cannot be empty"); + } + + void TClientSettings::FixSpaces(TStringBuf& str) { + while (str && isspace(str.back())) { + str.Chop(1); + } + } +} diff --git a/library/cpp/tvmauth/client/misc/tool/settings.h b/library/cpp/tvmauth/client/misc/tool/settings.h new file mode 100644 index 0000000000..67c3959263 --- /dev/null +++ b/library/cpp/tvmauth/client/misc/tool/settings.h @@ -0,0 +1,169 @@ +#pragma once + +#include <library/cpp/tvmauth/client/misc/settings.h> + +#include <library/cpp/tvmauth/client/exception.h> + +#include <library/cpp/tvmauth/checked_user_ticket.h> + +#include <util/datetime/base.h> +#include <util/generic/maybe.h> + +namespace NTvmAuth::NTvmTool { + /** + * Uses local http-interface to get state: http://localhost/tvm/. + * This interface can be provided with tvmtool (local daemon) or Qloud/YP (local http api in container). + * See more: https://wiki.yandex-team.ru/passport/tvm2/qloud/. + * + * Most part of settings will be fetched from tvmtool on start of client. + * You need to use aliases for TVM-clients (src and dst) which you specified in tvmtool or Qloud/YP interface + */ + class TClientSettings: public NTvmAuth::TClientSettings { + public: + /*! + * Sets default values: + * - hostname == "localhost" + * - port detected with env["DEPLOY_TVM_TOOL_URL"] (provided with Yandex.Deploy), + * otherwise port == 1 (it is ok for Qloud) + * - authToken: env["TVMTOOL_LOCAL_AUTHTOKEN"] (provided with Yandex.Deploy), + * otherwise env["QLOUD_TVM_TOKEN"] (provided with Qloud) + * + * AuthToken is protection from SSRF. + * + * @param selfAias - alias for your TVM client, which you specified in tvmtool or YD interface + */ + TClientSettings(const TAlias& selfAias); + + /*! + * Look at comment for ctor + * @param port + */ + TClientSettings& SetPort(ui16 port) { + Port_ = port; + return *this; + } + + /*! + * Default value: hostname == "localhost" + * @param hostname + */ + TClientSettings& SetHostname(const TString& hostname) { + Y_ENSURE_EX(hostname, TBrokenTvmClientSettings() << "Hostname cannot be empty"); + Hostname_ = hostname; + return *this; + } + + TClientSettings& SetSocketTimeout(TDuration socketTimeout) { + SocketTimeout_ = socketTimeout; + return *this; + } + + TClientSettings& SetConnectTimeout(TDuration connectTimeout) { + ConnectTimeout_ = connectTimeout; + return *this; + } + + /*! + * Look at comment for ctor + * @param token + */ + TClientSettings& SetAuthToken(TStringBuf token) { + FixSpaces(token); + Y_ENSURE_EX(token, TBrokenTvmClientSettings() << "Auth token cannot be empty"); + AuthToken_ = token; + return *this; + } + + /*! + * Blackbox environmet is provided by tvmtool for client. + * You can override it for your purpose with limitations: + * (env from tvmtool) -> (override) + * - Prod/ProdYateam -> Prod/ProdYateam + * - Test/TestYateam -> Test/TestYateam + * - Stress -> Stress + * + * You can contact tvm-dev@yandex-team.ru if limitations are too strict + * @param env + */ + TClientSettings& OverrideBlackboxEnv(EBlackboxEnv env) { + BbEnv_ = env; + return *this; + } + + /*! + * By default client checks src from ServiceTicket or default uid from UserTicket - + * to prevent you from forgetting to check it yourself. + * It does binary checks only: + * ticket gets status NoRoles, if there is no role for src or default uid. + * You need to check roles on your own if you have a non-binary role system or + * you have disabled ShouldCheckSrc/ShouldCheckDefaultUid + * + * You may need to disable this check in the following cases: + * - You use GetRoles() to provide verbose message (with revision). + * Double check may be inconsistent: + * binary check inside client uses revision of roles X - i.e. src 100500 has no role, + * exact check in your code uses revision of roles Y - i.e. src 100500 has some roles. + */ + bool ShouldCheckSrc = true; + bool ShouldCheckDefaultUid = true; + + // DEPRECATED API + // TODO: get rid of it: PASSP-35377 + public: + // Deprecated: set attributes directly + TClientSettings& SetShouldCheckSrc(bool val = true) { + ShouldCheckSrc = val; + return *this; + } + + // Deprecated: set attributes directly + TClientSettings& SetSShouldCheckDefaultUid(bool val = true) { + ShouldCheckDefaultUid = val; + return *this; + } + + public: // for TAsyncUpdaterBase + const TAlias& GetSelfAlias() const { + return SelfAias_; + } + + const TString& GetHostname() const { + return Hostname_; + } + + ui16 GetPort() const { + return Port_; + } + + TDuration GetSocketTimeout() const { + return SocketTimeout_; + } + + TDuration GetConnectTimeout() const { + return ConnectTimeout_; + } + + const TString& GetAuthToken() const { + Y_ENSURE_EX(AuthToken_, TBrokenTvmClientSettings() + << "Auth token cannot be empty. " + << "Env 'TVMTOOL_LOCAL_AUTHTOKEN' and 'QLOUD_TVM_TOKEN' are empty."); + return AuthToken_; + } + + TMaybe<EBlackboxEnv> GetOverridedBlackboxEnv() const { + return BbEnv_; + } + + private: + void FixSpaces(TStringBuf& str); + + private: + TAlias SelfAias_; + TString Hostname_; + ui16 Port_; + TDuration SocketTimeout_; + TDuration ConnectTimeout_; + TString AuthToken_; + TMaybe<EBlackboxEnv> BbEnv_; + }; +} diff --git a/library/cpp/tvmauth/client/misc/tool/threaded_updater.cpp b/library/cpp/tvmauth/client/misc/tool/threaded_updater.cpp new file mode 100644 index 0000000000..35bbe4f617 --- /dev/null +++ b/library/cpp/tvmauth/client/misc/tool/threaded_updater.cpp @@ -0,0 +1,370 @@ +#include "threaded_updater.h" + +#include <library/cpp/tvmauth/client/misc/utils.h> + +#include <library/cpp/json/json_reader.h> + +#include <util/generic/hash_set.h> +#include <util/stream/str.h> +#include <util/string/ascii.h> +#include <util/string/builder.h> +#include <util/string/cast.h> + +namespace NTvmAuth::NTvmTool { + TAsyncUpdaterPtr TThreadedUpdater::Create(const TClientSettings& settings, TLoggerPtr logger) { + Y_ENSURE_EX(logger, TNonRetriableException() << "Logger is required"); + THolder<TThreadedUpdater> p(new TThreadedUpdater( + settings.GetHostname(), + settings.GetPort(), + settings.GetSocketTimeout(), + settings.GetConnectTimeout(), + std::move(logger))); + p->Init(settings); + p->StartWorker(); + return p.Release(); + } + + TThreadedUpdater::~TThreadedUpdater() { + StopWorker(); // Required here to avoid using of deleted members + } + + TClientStatus TThreadedUpdater::GetStatus() const { + const TClientStatus::ECode state = GetState(); + return TClientStatus(state, GetLastError(state == TClientStatus::Ok)); + } + + NRoles::TRolesPtr TThreadedUpdater::GetRoles() const { + Y_ENSURE_EX(RolesFetcher_, + TBrokenTvmClientSettings() << "Roles were not configured in settings"); + return RolesFetcher_->GetCurrentRoles(); + } + + TClientStatus::ECode TThreadedUpdater::GetState() const { + const TInstant now = TInstant::Now(); + const TMetaInfo::TConfigPtr config = MetaInfo_.GetConfig(); + + if ((config->AreTicketsRequired() && AreServiceTicketsInvalid(now)) || ArePublicKeysInvalid(now)) { + return TClientStatus::Error; + } + + if (config->AreTicketsRequired()) { + if (!GetCachedServiceTickets() || config->DstAliases.size() > GetCachedServiceTickets()->TicketsByAlias.size()) { + return TClientStatus::Error; + } + } + + const TDuration st = now - GetUpdateTimeOfServiceTickets(); + const TDuration pk = now - GetUpdateTimeOfPublicKeys(); + + if ((config->AreTicketsRequired() && st > ServiceTicketsDurations_.Expiring) || pk > PublicKeysDurations_.Expiring) { + return TClientStatus::Warning; + } + + if (RolesFetcher_ && RolesFetcher_->ShouldWarn(now - GetUpdateTimeOfRoles())) { + return TClientStatus::Warning; + } + + if (IsConfigWarnTime()) { + return TClientStatus::Warning; + } + + return TClientStatus::Ok; + } + + TThreadedUpdater::TThreadedUpdater(const TString& host, ui16 port, TDuration socketTimeout, TDuration connectTimeout, TLoggerPtr logger) + : TThreadedUpdaterBase(TDuration::Seconds(5), logger, host, port, socketTimeout, connectTimeout) + , MetaInfo_(logger) + , ConfigWarnDelay_(TDuration::Seconds(30)) + { + ServiceTicketsDurations_.RefreshPeriod = TDuration::Minutes(10); + PublicKeysDurations_.RefreshPeriod = TDuration::Minutes(10); + } + + void TThreadedUpdater::Init(const TClientSettings& settings) { + const TMetaInfo::TConfigPtr config = MetaInfo_.Init(GetClient(), settings); + LastVisitForConfig_ = TInstant::Now(); + + SetBbEnv(config->BbEnv, settings.GetOverridedBlackboxEnv()); + if (settings.GetOverridedBlackboxEnv()) { + LogInfo(TStringBuilder() + << "Meta: override blackbox env: " << config->BbEnv + << "->" << *settings.GetOverridedBlackboxEnv()); + } + + if (config->IdmSlug) { + RolesFetcher_ = std::make_unique<TRolesFetcher>( + TRolesFetcherSettings{ + .SelfAlias = settings.GetSelfAlias(), + }, + Logger_); + } + + ui8 tries = 3; + do { + UpdateState(); + } while (!IsEverythingOk(*config) && --tries > 0); + + if (!IsEverythingOk(*config)) { + ThrowLastError(); + } + } + + void TThreadedUpdater::UpdateState() { + bool wasUpdated = false; + try { + wasUpdated = MetaInfo_.TryUpdateConfig(GetClient()); + LastVisitForConfig_ = TInstant::Now(); + ClearError(EScope::TvmtoolConfig); + } catch (const std::exception& e) { + ProcessError(EType::Retriable, EScope::TvmtoolConfig, e.what()); + LogWarning(TStringBuilder() << "Error while fetching of tvmtool config: " << e.what()); + } + if (IsConfigWarnTime()) { + LogError(TStringBuilder() << "Tvmtool config have not been refreshed for too long period"); + } + + TMetaInfo::TConfigPtr config = MetaInfo_.GetConfig(); + + if (wasUpdated || IsTimeToUpdateServiceTickets(*config, LastVisitForServiceTickets_)) { + try { + const TInstant updateTime = UpdateServiceTickets(*config); + SetUpdateTimeOfServiceTickets(updateTime); + LastVisitForServiceTickets_ = TInstant::Now(); + + if (AreServiceTicketsOk(*config)) { + ClearError(EScope::ServiceTickets); + } + LogDebug(TStringBuilder() << "Tickets fetched from tvmtool: " << updateTime); + } catch (const std::exception& e) { + ProcessError(EType::Retriable, EScope::ServiceTickets, e.what()); + LogWarning(TStringBuilder() << "Error while fetching of tickets: " << e.what()); + } + + if (TInstant::Now() - GetUpdateTimeOfServiceTickets() > ServiceTicketsDurations_.Expiring) { + LogError("Service tickets have not been refreshed for too long period"); + } + } + + if (wasUpdated || IsTimeToUpdatePublicKeys(LastVisitForPublicKeys_)) { + try { + const TInstant updateTime = UpdateKeys(*config); + SetUpdateTimeOfPublicKeys(updateTime); + LastVisitForPublicKeys_ = TInstant::Now(); + + if (ArePublicKeysOk()) { + ClearError(EScope::PublicKeys); + } + LogDebug(TStringBuilder() << "Public keys fetched from tvmtool: " << updateTime); + } catch (const std::exception& e) { + ProcessError(EType::Retriable, EScope::PublicKeys, e.what()); + LogWarning(TStringBuilder() << "Error while fetching of public keys: " << e.what()); + } + + if (TInstant::Now() - GetUpdateTimeOfPublicKeys() > PublicKeysDurations_.Expiring) { + LogError("Public keys have not been refreshed for too long period"); + } + } + + if (RolesFetcher_ && (wasUpdated || RolesFetcher_->IsTimeToUpdate(TInstant::Now() - GetUpdateTimeOfRoles()))) { + try { + RolesFetcher_->Update(RolesFetcher_->FetchActualRoles(MetaInfo_.GetAuthHeader(), GetClient())); + SetUpdateTimeOfRoles(TInstant::Now()); + + if (RolesFetcher_->AreRolesOk()) { + ClearError(EScope::Roles); + } + } catch (const std::exception& e) { + ProcessError(EType::Retriable, EScope::Roles, e.what()); + LogWarning(TStringBuilder() << "Failed to update roles: " << e.what()); + } + + if (RolesFetcher_->ShouldWarn(TInstant::Now() - GetUpdateTimeOfRoles())) { + LogError("Roles have not been refreshed for too long period"); + } + } + } + + TInstant TThreadedUpdater::UpdateServiceTickets(const TMetaInfo::TConfig& config) { + const std::pair<TString, TInstant> tickets = FetchServiceTickets(config); + + if (TInstant::Now() - tickets.second >= ServiceTicketsDurations_.Invalid) { + throw yexception() << "Service tickets are too old: " << tickets.second; + } + + TPairTicketsErrors p = ParseFetchTicketsResponse(tickets.first, config.DstAliases); + SetServiceTickets(MakeIntrusiveConst<TServiceTickets>(std::move(p.Tickets), + std::move(p.Errors), + config.DstAliases)); + return tickets.second; + } + + std::pair<TString, TInstant> TThreadedUpdater::FetchServiceTickets(const TMetaInfo::TConfig& config) const { + TStringStream s; + THttpHeaders headers; + + const TString request = TMetaInfo::GetRequestForTickets(config); + auto code = GetClient().DoGet(request, &s, MetaInfo_.GetAuthHeader(), &headers); + Y_ENSURE(code == 200, ProcessHttpError(EScope::ServiceTickets, request, code, s.Str())); + + return {s.Str(), GetBirthTimeFromResponse(headers, "tickets")}; + } + + static THashSet<TTvmId> GetAllTvmIds(const TMetaInfo::TDstAliases& dsts) { + THashSet<TTvmId> res; + res.reserve(dsts.size()); + + for (const auto& pair : dsts) { + res.insert(pair.second); + } + + return res; + } + + TAsyncUpdaterBase::TPairTicketsErrors TThreadedUpdater::ParseFetchTicketsResponse(const TString& resp, + const TMetaInfo::TDstAliases& dsts) const { + const THashSet<TTvmId> allTvmIds = GetAllTvmIds(dsts); + + TServiceTickets::TMapIdStr tickets; + TServiceTickets::TMapIdStr errors; + + auto procErr = [this](const TString& msg) { + ProcessError(EType::NonRetriable, EScope::ServiceTickets, msg); + LogError(msg); + }; + + NJson::TJsonValue doc; + Y_ENSURE(NJson::ReadJsonTree(resp, &doc), "Invalid json from tvmtool: " << resp); + + for (const auto& pair : doc.GetMap()) { + NJson::TJsonValue tvmId; + unsigned long long tvmIdNum = 0; + + if (!pair.second.GetValue("tvm_id", &tvmId) || + !tvmId.GetUInteger(&tvmIdNum)) { + procErr(TStringBuilder() + << "Failed to get 'tvm_id' from key, should never happend '" + << pair.first << "': " << resp); + continue; + } + + if (!allTvmIds.contains(tvmIdNum)) { + continue; + } + + NJson::TJsonValue val; + if (!pair.second.GetValue("ticket", &val)) { + TString err; + if (pair.second.GetValue("error", &val)) { + err = val.GetString(); + } else { + err = "Failed to get 'ticket' and 'error', should never happend: " + pair.first; + } + + procErr(TStringBuilder() + << "Failed to get ServiceTicket for " << pair.first + << " (" << tvmIdNum << "): " << err); + + errors.insert({tvmIdNum, std::move(err)}); + continue; + } + + tickets.insert({tvmIdNum, val.GetString()}); + } + + // This work-around is required because of bug in old verions of tvmtool: PASSP-24829 + for (const auto& pair : dsts) { + if (!tickets.contains(pair.second) && !errors.contains(pair.second)) { + TString err = "Missing tvm_id in response, should never happend: " + pair.first; + + procErr(TStringBuilder() + << "Failed to get ServiceTicket for " << pair.first + << " (" << pair.second << "): " << err); + + errors.emplace(pair.second, std::move(err)); + } + } + + return {std::move(tickets), std::move(errors)}; + } + + TInstant TThreadedUpdater::UpdateKeys(const TMetaInfo::TConfig& config) { + const std::pair<TString, TInstant> keys = FetchPublicKeys(); + + if (TInstant::Now() - keys.second >= PublicKeysDurations_.Invalid) { + throw yexception() << "Public keys are too old: " << keys.second; + } + + SetServiceContext(MakeIntrusiveConst<TServiceContext>( + TServiceContext::CheckingFactory(config.SelfTvmId, keys.first))); + SetUserContext(keys.first); + + return keys.second; + } + + std::pair<TString, TInstant> TThreadedUpdater::FetchPublicKeys() const { + TStringStream s; + THttpHeaders headers; + + auto code = GetClient().DoGet("/tvm/keys", &s, MetaInfo_.GetAuthHeader(), &headers); + Y_ENSURE(code == 200, ProcessHttpError(EScope::PublicKeys, "/tvm/keys", code, s.Str())); + + return {s.Str(), GetBirthTimeFromResponse(headers, "public keys")}; + } + + TInstant TThreadedUpdater::GetBirthTimeFromResponse(const THttpHeaders& headers, TStringBuf errMsg) { + auto it = std::find_if(headers.begin(), + headers.end(), + [](const THttpInputHeader& h) { + return AsciiEqualsIgnoreCase(h.Name(), "X-Ya-Tvmtool-Data-Birthtime"); + }); + Y_ENSURE(it != headers.end(), "Failed to fetch bithtime of " << errMsg << " from tvmtool"); + + ui64 time = 0; + Y_ENSURE(TryIntFromString<10>(it->Value(), time), + "Bithtime of " << errMsg << " from tvmtool must be unixtime. Got: " << it->Value()); + + return TInstant::Seconds(time); + } + + bool TThreadedUpdater::IsTimeToUpdateServiceTickets(const TMetaInfo::TConfig& config, + TInstant lastUpdate) const { + return config.AreTicketsRequired() && + TInstant::Now() - lastUpdate > ServiceTicketsDurations_.RefreshPeriod; + } + + bool TThreadedUpdater::IsTimeToUpdatePublicKeys(TInstant lastUpdate) const { + return TInstant::Now() - lastUpdate > PublicKeysDurations_.RefreshPeriod; + } + + bool TThreadedUpdater::IsEverythingOk(const TMetaInfo::TConfig& config) const { + if (RolesFetcher_ && !RolesFetcher_->AreRolesOk()) { + return false; + } + return AreServiceTicketsOk(config) && ArePublicKeysOk(); + } + + bool TThreadedUpdater::AreServiceTicketsOk(const TMetaInfo::TConfig& config) const { + return AreServiceTicketsOk(config.DstAliases.size()); + } + + bool TThreadedUpdater::AreServiceTicketsOk(size_t requiredCount) const { + if (requiredCount == 0) { + return true; + } + + auto c = GetCachedServiceTickets(); + return c && c->TicketsByAlias.size() == requiredCount; + } + + bool TThreadedUpdater::ArePublicKeysOk() const { + return GetCachedServiceContext() && GetCachedUserContext(); + } + + bool TThreadedUpdater::IsConfigWarnTime() const { + return LastVisitForConfig_ + ConfigWarnDelay_ < TInstant::Now(); + } + + void TThreadedUpdater::Worker() { + UpdateState(); + } +} diff --git a/library/cpp/tvmauth/client/misc/tool/threaded_updater.h b/library/cpp/tvmauth/client/misc/tool/threaded_updater.h new file mode 100644 index 0000000000..57f97f5442 --- /dev/null +++ b/library/cpp/tvmauth/client/misc/tool/threaded_updater.h @@ -0,0 +1,58 @@ +#pragma once + +#include "meta_info.h" +#include "roles_fetcher.h" + +#include <library/cpp/tvmauth/client/misc/async_updater.h> +#include <library/cpp/tvmauth/client/misc/threaded_updater.h> + +#include <atomic> + +namespace NTvmAuth::NTvmTool { + class TThreadedUpdater: public TThreadedUpdaterBase { + public: + static TAsyncUpdaterPtr Create(const TClientSettings& settings, TLoggerPtr logger); + ~TThreadedUpdater(); + + TClientStatus GetStatus() const override; + NRoles::TRolesPtr GetRoles() const override; + + protected: // for tests + TClientStatus::ECode GetState() const; + + TThreadedUpdater(const TString& host, ui16 port, TDuration socketTimeout, TDuration connectTimeout, TLoggerPtr logger); + + void Init(const TClientSettings& settings); + void UpdateState(); + + TInstant UpdateServiceTickets(const TMetaInfo::TConfig& config); + std::pair<TString, TInstant> FetchServiceTickets(const TMetaInfo::TConfig& config) const; + TPairTicketsErrors ParseFetchTicketsResponse(const TString& resp, + const TMetaInfo::TDstAliases& dsts) const; + + TInstant UpdateKeys(const TMetaInfo::TConfig& config); + std::pair<TString, TInstant> FetchPublicKeys() const; + + static TInstant GetBirthTimeFromResponse(const THttpHeaders& headers, TStringBuf errMsg); + + bool IsTimeToUpdateServiceTickets(const TMetaInfo::TConfig& config, TInstant lastUpdate) const; + bool IsTimeToUpdatePublicKeys(TInstant lastUpdate) const; + + bool IsEverythingOk(const TMetaInfo::TConfig& config) const; + bool AreServiceTicketsOk(const TMetaInfo::TConfig& config) const; + bool AreServiceTicketsOk(size_t requiredCount) const; + bool ArePublicKeysOk() const; + bool IsConfigWarnTime() const; + + private: + void Worker() override; + + protected: + TMetaInfo MetaInfo_; + TInstant LastVisitForServiceTickets_; + TInstant LastVisitForPublicKeys_; + TInstant LastVisitForConfig_; + TDuration ConfigWarnDelay_; + std::unique_ptr<TRolesFetcher> RolesFetcher_; + }; +} diff --git a/library/cpp/tvmauth/client/misc/utils.cpp b/library/cpp/tvmauth/client/misc/utils.cpp new file mode 100644 index 0000000000..a124c7b11c --- /dev/null +++ b/library/cpp/tvmauth/client/misc/utils.cpp @@ -0,0 +1,46 @@ +#include "utils.h" + +#include <library/cpp/tvmauth/client/facade.h> + +#include <util/stream/format.h> + +namespace NTvmAuth::NInternal { + void TClientCaningKnife::StartTvmClientStopping(TTvmClient* c) { + if (c && c->Updater_) { + c->Updater_->StartTvmClientStopping(); + } + } + + bool TClientCaningKnife::IsTvmClientStopped(TTvmClient* c) { + return c && c->Updater_ ? c->Updater_->IsTvmClientStopped() : true; + } +} + +namespace NTvmAuth::NUtils { + TString ToHex(const TStringBuf s) { + TStringStream res; + res.Reserve(2 * s.size()); + + for (char c : s) { + res << Hex(c, HF_FULL); + } + + return std::move(res.Str()); + } + + bool CheckBbEnvOverriding(EBlackboxEnv original, EBlackboxEnv override) noexcept { + switch (original) { + case EBlackboxEnv::Prod: + case EBlackboxEnv::ProdYateam: + return override == EBlackboxEnv::Prod || override == EBlackboxEnv::ProdYateam; + case EBlackboxEnv::Test: + return true; + case EBlackboxEnv::TestYateam: + return override == EBlackboxEnv::Test || override == EBlackboxEnv::TestYateam; + case EBlackboxEnv::Stress: + return override == EBlackboxEnv::Stress; + } + + return false; + } +} diff --git a/library/cpp/tvmauth/client/misc/utils.h b/library/cpp/tvmauth/client/misc/utils.h new file mode 100644 index 0000000000..1aa5e61bf1 --- /dev/null +++ b/library/cpp/tvmauth/client/misc/utils.h @@ -0,0 +1,95 @@ +#pragma once + +#include "api/settings.h" +#include "tool/settings.h" + +#include <util/string/cast.h> +#include <util/system/spinlock.h> + +#include <optional> + +namespace NTvmAuth { + class TTvmClient; +} + +namespace NTvmAuth::NInternal { + class TClientCaningKnife { + public: + static void StartTvmClientStopping(TTvmClient* c); + static bool IsTvmClientStopped(TTvmClient* c); + }; +} + +namespace NTvmAuth::NUtils { + TString ToHex(const TStringBuf s); + + inline NTvmAuth::NTvmApi::TClientSettings::TDstMap ParseDstMap(TStringBuf dsts) { + NTvmAuth::NTvmApi::TClientSettings::TDstMap res; + + while (dsts) { + TStringBuf pair = dsts.NextTok(';'); + TStringBuf alias = pair.NextTok(':'); + res.insert(decltype(res)::value_type( + alias, + IntFromString<TTvmId, 10>(pair))); + } + + return res; + } + + inline NTvmAuth::NTvmApi::TClientSettings::TDstVector ParseDstVector(TStringBuf dsts) { + NTvmAuth::NTvmApi::TClientSettings::TDstVector res; + + while (dsts) { + res.push_back(IntFromString<TTvmId, 10>(dsts.NextTok(';'))); + } + + return res; + } + + bool CheckBbEnvOverriding(EBlackboxEnv original, EBlackboxEnv override) noexcept; + + template <class T> + class TProtectedValue { + class TAssignOp { + public: + static void Assign(T& l, const T& r) { + l = r; + } + + template <typename U> + static void Assign(std::shared_ptr<U>& l, std::shared_ptr<U>& r) { + l.swap(r); + } + + template <typename U> + static void Assign(TIntrusiveConstPtr<U>& l, TIntrusiveConstPtr<U>& r) { + l.Swap(r); + } + }; + + public: + TProtectedValue() = default; + + TProtectedValue(T value) + : Value_(value) + { + } + + T Get() const { + with_lock (Lock_) { + return Value_; + } + } + + void Set(T o) { + with_lock (Lock_) { + TAssignOp::Assign(Value_, o); + } + } + + private: + T Value_; + mutable TAdaptiveLock Lock_; + }; +} diff --git a/library/cpp/tvmauth/client/mocked_updater.cpp b/library/cpp/tvmauth/client/mocked_updater.cpp new file mode 100644 index 0000000000..54f94bc92a --- /dev/null +++ b/library/cpp/tvmauth/client/mocked_updater.cpp @@ -0,0 +1,60 @@ +#include "mocked_updater.h" + +#include <library/cpp/tvmauth/unittest.h> + +namespace NTvmAuth { + TMockedUpdater::TSettings TMockedUpdater::TSettings::CreateDeafult() { + TMockedUpdater::TSettings res; + + res.SelfTvmId = 100500; + + res.Backends = { + { + /*.Alias_ = */ "my_dest", + /*.Id_ = */ 42, + /*.Value_ = */ "3:serv:CBAQ__________9_IgYIlJEGECo:O9-vbod_8czkKrpwJAZCI8UgOIhNr2xKPcS-LWALrVC224jga2nIT6vLiw6q3d6pAT60g9K7NB39LEmh7vMuePtUMjzuZuL-uJg17BsH2iTLCZSxDjWxbU9piA2T6u607jiSyiy-FI74pEPqkz7KKJ28aPsefuC1VUweGkYFzNY", + }, + }; + + res.BadBackends = { + { + /*.Alias_ = */ "my_bad_dest", + /*.Id_ = */ 43, + /*.Value_ = */ "Dst is not found", + }, + }; + + return res; + } + + TMockedUpdater::TMockedUpdater(const TSettings& settings) + : Roles_(settings.Roles) + { + SetServiceContext(MakeIntrusiveConst<TServiceContext>(TServiceContext::CheckingFactory( + settings.SelfTvmId, + NUnittest::TVMKNIFE_PUBLIC_KEYS))); + + SetBbEnv(settings.UserTicketEnv); + SetUserContext(NUnittest::TVMKNIFE_PUBLIC_KEYS); + + TServiceTickets::TMapIdStr tickets, errors; + TServiceTickets::TMapAliasId aliases; + + for (const TSettings::TTuple& t : settings.Backends) { + tickets[t.Id] = t.Value; + aliases[t.Alias] = t.Id; + } + for (const TSettings::TTuple& t : settings.BadBackends) { + errors[t.Id] = t.Value; + aliases[t.Alias] = t.Id; + } + + SetServiceTickets(MakeIntrusiveConst<TServiceTickets>( + std::move(tickets), + std::move(errors), + std::move(aliases))); + + SetUpdateTimeOfPublicKeys(TInstant::Now()); + SetUpdateTimeOfServiceTickets(TInstant::Now()); + } +} diff --git a/library/cpp/tvmauth/client/mocked_updater.h b/library/cpp/tvmauth/client/mocked_updater.h new file mode 100644 index 0000000000..c9a5daf3cb --- /dev/null +++ b/library/cpp/tvmauth/client/mocked_updater.h @@ -0,0 +1,44 @@ +#pragma once + +#include "misc/async_updater.h" + +namespace NTvmAuth { + class TMockedUpdater: public TAsyncUpdaterBase { + public: + struct TSettings { + struct TTuple { + TClientSettings::TAlias Alias; + TTvmId Id = 0; + TString Value; // ticket or error + }; + + TTvmId SelfTvmId = 0; + TVector<TTuple> Backends; + TVector<TTuple> BadBackends; + EBlackboxEnv UserTicketEnv = EBlackboxEnv::Test; + NRoles::TRolesPtr Roles; + + static TSettings CreateDeafult(); + }; + + TMockedUpdater(const TSettings& settings = TSettings::CreateDeafult()); + + TClientStatus GetStatus() const override { + return TClientStatus(); + } + + NRoles::TRolesPtr GetRoles() const override { + Y_ENSURE_EX(Roles_, TIllegalUsage() << "Roles are not provided"); + return Roles_; + } + + using TAsyncUpdaterBase::SetServiceContext; + using TAsyncUpdaterBase::SetServiceTickets; + using TAsyncUpdaterBase::SetUpdateTimeOfPublicKeys; + using TAsyncUpdaterBase::SetUpdateTimeOfServiceTickets; + using TAsyncUpdaterBase::SetUserContext; + + protected: + NRoles::TRolesPtr Roles_; + }; +} diff --git a/library/cpp/tvmauth/client/ut/async_updater_ut.cpp b/library/cpp/tvmauth/client/ut/async_updater_ut.cpp new file mode 100644 index 0000000000..1c1e8cbaae --- /dev/null +++ b/library/cpp/tvmauth/client/ut/async_updater_ut.cpp @@ -0,0 +1,165 @@ +#include "common.h" + +#include <library/cpp/tvmauth/client/misc/async_updater.h> + +#include <library/cpp/tvmauth/unittest.h> + +#include <library/cpp/testing/unittest/registar.h> + +using namespace NTvmAuth; + +Y_UNIT_TEST_SUITE(AsyncUpdater) { + static const TString SRV_TICKET = "3:serv:CBAQ__________9_IgYIexCUkQY:GioCM49Ob6_f80y6FY0XBVN4hLXuMlFeyMvIMiDuQnZkbkLpRpQOuQo5YjWoBjM0Vf-XqOm8B7xtrvxSYHDD7Q4OatN2l-Iwg7i71lE3scUeD36x47st3nd0OThvtjrFx_D8mw_c0GT5KcniZlqq1SjhLyAk1b_zJsx8viRAhCU"; + static const TString PROD_TICKET = "3:user:CAsQ__________9_Gg4KAgh7EHsg0oXYzAQoAA:N8PvrDNLh-5JywinxJntLeQGDEHBUxfzjuvB8-_BEUv1x9CALU7do8irDlDYVeVVDr4AIpR087YPZVzWPAqmnBuRJS0tJXekmDDvrivLnbRrzY4IUXZ_fImB0fJhTyVetKv6RD11bGqnAJeDpIukBwPTbJc_EMvKDt8V490CJFw"; + static const TString TEST_TICKET = "3:user:CA0Q__________9_Gg4KAgh7EHsg0oXYzAQoAQ:FSADps3wNGm92Vyb1E9IVq5M6ZygdGdt1vafWWEhfDDeCLoVA-sJesxMl2pGW4OxJ8J1r_MfpG3ZoBk8rLVMHUFrPa6HheTbeXFAWl8quEniauXvKQe4VyrpA1SPgtRoFqi5upSDIJzEAe1YRJjq1EClQ_slMt8R0kA_JjKUX54"; + static const TString PROD_YATEAM_TICKET = "3:user:CAwQ__________9_Gg4KAgh7EHsg0oXYzAQoAg:M9dEFEWHLHXiL7brCsyfYlm254PE6VeshUjI62u2qMDRzt6-0jAoJTIdDiogerItht1YFYSn8fSqmMf23_rueGj-wkmvyNzbcBSk3jtK2U5sai_W0bK6OwukR9tzWzi1Gcgg9DrNEeIKFvs1EBqYCF4mPHWo5bgk0CR580Cgit4"; + static const TString TEST_YATEAM_TICKET = "3:user:CA4Q__________9_Gg4KAgh7EHsg0oXYzAQoAw:IlaV3htk3jYrviIOz3k3Dfwz7p-bYYpbrgdn53GiUrMGdrT9eobHeuzNvPLrWB0yuYZAD46C3MGxok4GGmHhT73mki4XOCX8yWT4jW_hzcHBik1442tjWwh8IWqV_7q5j5496suVuLWjnZORWbb7I-2iwdIlU1BUiDfhoAolCq8"; + static const TString STRESS_TICKET = "3:user:CA8Q__________9_Gg4KAgh7EHsg0oXYzAQoBA:GBuG_TLo6SL2OYFxp7Zly04HPNzmAF7Fu2E8E9SnwQDoxq9rf7VThSPtTmnBSAl5UVRRPkMsRtzzHZ87qtj6l-PvF0K7PrDu7-yS_xiFTgAl9sEfXAIHJVzZLoksGRgpoBtpBUg9vVaJsPns0kWFKJgq8M-Mk9agrSk7sb2VUeQ"; + + class TTestUpdater: public TAsyncUpdaterBase { + public: + using TAsyncUpdaterBase::SetBbEnv; + using TAsyncUpdaterBase::SetUserContext; + + TClientStatus GetStatus() const override { + return TClientStatus(); + } + }; + + Y_UNIT_TEST(User) { + TTestUpdater u; + + UNIT_ASSERT(!u.GetCachedUserContext()); + + u.SetUserContext(NUnittest::TVMKNIFE_PUBLIC_KEYS); + UNIT_ASSERT(!u.GetCachedUserContext()); + + UNIT_ASSERT_NO_EXCEPTION(u.SetBbEnv(EBlackboxEnv::Prod)); + UNIT_ASSERT(u.GetCachedUserContext()); + UNIT_ASSERT(u.GetCachedUserContext()->Check(PROD_TICKET)); + UNIT_ASSERT_NO_EXCEPTION(u.GetCachedUserContext(EBlackboxEnv::ProdYateam)); + UNIT_ASSERT(u.GetCachedUserContext(EBlackboxEnv::ProdYateam)->Check(PROD_YATEAM_TICKET)); + + UNIT_ASSERT_EXCEPTION_CONTAINS(u.SetBbEnv(EBlackboxEnv::Prod, EBlackboxEnv::Test), + TBrokenTvmClientSettings, + "Overriding of BlackboxEnv is illegal: Prod -> Test"); + UNIT_ASSERT_EXCEPTION_CONTAINS(u.GetCachedUserContext(EBlackboxEnv::Test), + TBrokenTvmClientSettings, + "Overriding of BlackboxEnv is illegal: Prod -> Test"); + + UNIT_ASSERT(u.GetCachedUserContext()); + UNIT_ASSERT(u.GetCachedUserContext()->Check(PROD_TICKET)); + } + + class DummyUpdater: public TAsyncUpdaterBase { + public: + TClientStatus GetStatus() const override { + return TClientStatus(); + } + + using TAsyncUpdaterBase::SetServiceContext; + using TAsyncUpdaterBase::SetServiceTickets; + using TAsyncUpdaterBase::SetUserContext; + }; + + Y_UNIT_TEST(Cache) { + DummyUpdater d; + + UNIT_ASSERT(!d.GetCachedServiceTickets()); + TServiceTicketsPtr st = MakeIntrusiveConst<TServiceTickets>(TServiceTickets::TMapIdStr(), + TServiceTickets::TMapIdStr(), + TServiceTickets::TMapAliasId()); + d.SetServiceTickets(st); + UNIT_ASSERT_EQUAL(st.Get(), d.GetCachedServiceTickets().Get()); + + UNIT_ASSERT(!d.GetCachedServiceContext()); + TServiceContextPtr sc = MakeIntrusiveConst<TServiceContext>(TServiceContext::SigningFactory("kjndfadfndsfafdasd")); + d.SetServiceContext(sc); + UNIT_ASSERT_EQUAL(sc.Get(), d.GetCachedServiceContext().Get()); + + UNIT_ASSERT(!d.GetCachedUserContext()); + d.SetUserContext(NUnittest::TVMKNIFE_PUBLIC_KEYS); + } + + Y_UNIT_TEST(ServiceTickets_Aliases) { + using TId = TServiceTickets::TMapIdStr; + using TUnfetchedId = TServiceTickets::TIdSet; + using TStr = TServiceTickets::TMapAliasStr; + using TUnfetchedAlias = TServiceTickets::TAliasSet; + using TAls = TServiceTickets::TMapAliasId; + TServiceTickets t(TId{}, TId{}, TAls{}); + + UNIT_ASSERT_NO_EXCEPTION(t = TServiceTickets(TId({{1, "t1"}, {2, "t2"}}), + TId({{3, "e1"}}), + TAls())); + UNIT_ASSERT_EQUAL(TId({{1, "t1"}, {2, "t2"}}), t.TicketsById); + UNIT_ASSERT_EQUAL(TId({{3, "e1"}}), t.ErrorsById); + UNIT_ASSERT_EQUAL(TStr(), t.TicketsByAlias); + UNIT_ASSERT_EQUAL(TStr(), t.ErrorsByAlias); + + UNIT_ASSERT_NO_EXCEPTION(t = TServiceTickets(TId({{1, "t1"}, {2, "t2"}}), + TId({{3, "e1"}}), + TAls({{"1", 1}, {"2", 2}, {"3", 3}}))); + UNIT_ASSERT_EQUAL(TId({{1, "t1"}, {2, "t2"}}), t.TicketsById); + UNIT_ASSERT_EQUAL(TId({{3, "e1"}}), t.ErrorsById); + UNIT_ASSERT_EQUAL(TUnfetchedId(), t.UnfetchedIds); + UNIT_ASSERT_EQUAL(TStr({{"1", "t1"}, {"2", "t2"}}), t.TicketsByAlias); + UNIT_ASSERT_EQUAL(TStr({{"3", "e1"}}), t.ErrorsByAlias); + UNIT_ASSERT_EQUAL(TUnfetchedAlias({}), t.UnfetchedAliases); + } + + Y_UNIT_TEST(ServiceTickets_UnfetchedIds) { + using TId = TServiceTickets::TMapIdStr; + using TUnfetchedId = TServiceTickets::TIdSet; + using TStr = TServiceTickets::TMapAliasStr; + using TUnfetchedAlias = TServiceTickets::TAliasSet; + using TAls = TServiceTickets::TMapAliasId; + TServiceTickets t(TId({{1, "t1"}, {2, "t2"}}), + TId(), + TAls({{"1", 1}, {"2", 2}, {"3", 3}})); + + UNIT_ASSERT_EQUAL(TId({{1, "t1"}, {2, "t2"}}), t.TicketsById); + UNIT_ASSERT_EQUAL(TId({}), t.ErrorsById); + UNIT_ASSERT_EQUAL(TUnfetchedId({3}), t.UnfetchedIds); + UNIT_ASSERT_EQUAL(TUnfetchedAlias({{"3"}}), t.UnfetchedAliases); + UNIT_ASSERT_EQUAL(TStr({{"1", "t1"}, {"2", "t2"}}), t.TicketsByAlias); + UNIT_ASSERT_EQUAL(TStr(), t.ErrorsByAlias); + } + + Y_UNIT_TEST(ServiceTickets_InvalidationTime) { + using TId = TServiceTickets::TMapIdStr; + using TAls = TServiceTickets::TMapAliasId; + + TServiceTickets t(TId{}, TId{}, TAls{}); + UNIT_ASSERT_VALUES_EQUAL(TInstant(), t.InvalidationTime); + + UNIT_ASSERT_NO_EXCEPTION(t = TServiceTickets(TId({{1, SRV_TICKET}}), + TId(), + TAls())); + UNIT_ASSERT_VALUES_EQUAL(TInstant::Seconds(std::numeric_limits<time_t>::max()), t.InvalidationTime); + + UNIT_ASSERT_NO_EXCEPTION(t = TServiceTickets(TId({ + {1, SRV_TICKET}, + {2, "serv"}, + }), + TId(), + TAls())); + UNIT_ASSERT_VALUES_EQUAL(TInstant::Seconds(std::numeric_limits<time_t>::max()), t.InvalidationTime); + + UNIT_ASSERT_NO_EXCEPTION(t = TServiceTickets(TId({ + {2, "serv"}, + }), + TId(), + TAls())); + UNIT_ASSERT_VALUES_EQUAL(TInstant(), t.InvalidationTime); + + UNIT_ASSERT_NO_EXCEPTION(t = TServiceTickets(TId({ + {1, SRV_TICKET}, + {2, "serv"}, + {3, "3:serv:CBAQeyIECAMQAw:TiZjG2Ut9j-9n0zcqxGW8xiYmnFa-i10-dbA0FKIInKzeDuueovWVEBcgbQHndblzRCxoIBMgbotOf7ALk2xoSBnRbOKomAIEtiTBL77GByL5O8K_HUGNYb-ygqnmZlIuLalgeRQAdsKstgUwQzufnOQyekipmamwo7EVQhr8Ug"}, + }), + TId(), + TAls())); + UNIT_ASSERT_VALUES_EQUAL(TInstant::Seconds(123), t.InvalidationTime); + } +} diff --git a/library/cpp/tvmauth/client/ut/checker_ut.cpp b/library/cpp/tvmauth/client/ut/checker_ut.cpp new file mode 100644 index 0000000000..73f2b0dfda --- /dev/null +++ b/library/cpp/tvmauth/client/ut/checker_ut.cpp @@ -0,0 +1,189 @@ +#include "common.h" + +#include <library/cpp/tvmauth/client/mocked_updater.h> +#include <library/cpp/tvmauth/client/misc/checker.h> +#include <library/cpp/tvmauth/client/misc/getter.h> +#include <library/cpp/tvmauth/client/misc/api/threaded_updater.h> + +#include <library/cpp/tvmauth/type.h> + +#include <library/cpp/testing/unittest/registar.h> + +using namespace NTvmAuth; + +Y_UNIT_TEST_SUITE(ClientChecker) { + static const TTvmId OK_CLIENT = 100500; + static const TString PROD_TICKET = "3:user:CAsQ__________9_Gg4KAgh7EHsg0oXYzAQoAA:N8PvrDNLh-5JywinxJntLeQGDEHBUxfzjuvB8-_BEUv1x9CALU7do8irDlDYVeVVDr4AIpR087YPZVzWPAqmnBuRJS0tJXekmDDvrivLnbRrzY4IUXZ_fImB0fJhTyVetKv6RD11bGqnAJeDpIukBwPTbJc_EMvKDt8V490CJFw"; + static const TString TEST_TICKET = "3:user:CA0Q__________9_Gg4KAgh7EHsg0oXYzAQoAQ:FSADps3wNGm92Vyb1E9IVq5M6ZygdGdt1vafWWEhfDDeCLoVA-sJesxMl2pGW4OxJ8J1r_MfpG3ZoBk8rLVMHUFrPa6HheTbeXFAWl8quEniauXvKQe4VyrpA1SPgtRoFqi5upSDIJzEAe1YRJjq1EClQ_slMt8R0kA_JjKUX54"; + static const TString PROD_YATEAM_TICKET = "3:user:CAwQ__________9_Gg4KAgh7EHsg0oXYzAQoAg:M9dEFEWHLHXiL7brCsyfYlm254PE6VeshUjI62u2qMDRzt6-0jAoJTIdDiogerItht1YFYSn8fSqmMf23_rueGj-wkmvyNzbcBSk3jtK2U5sai_W0bK6OwukR9tzWzi1Gcgg9DrNEeIKFvs1EBqYCF4mPHWo5bgk0CR580Cgit4"; + static const TString TEST_YATEAM_TICKET = "3:user:CA4Q__________9_Gg4KAgh7EHsg0oXYzAQoAw:IlaV3htk3jYrviIOz3k3Dfwz7p-bYYpbrgdn53GiUrMGdrT9eobHeuzNvPLrWB0yuYZAD46C3MGxok4GGmHhT73mki4XOCX8yWT4jW_hzcHBik1442tjWwh8IWqV_7q5j5496suVuLWjnZORWbb7I-2iwdIlU1BUiDfhoAolCq8"; + static const TString STRESS_TICKET = "3:user:CA8Q__________9_Gg4KAgh7EHsg0oXYzAQoBA:GBuG_TLo6SL2OYFxp7Zly04HPNzmAF7Fu2E8E9SnwQDoxq9rf7VThSPtTmnBSAl5UVRRPkMsRtzzHZ87qtj6l-PvF0K7PrDu7-yS_xiFTgAl9sEfXAIHJVzZLoksGRgpoBtpBUg9vVaJsPns0kWFKJgq8M-Mk9agrSk7sb2VUeQ"; + static const TString SRV_TICKET = "3:serv:CBAQ__________9_IgYIexCUkQY:GioCM49Ob6_f80y6FY0XBVN4hLXuMlFeyMvIMiDuQnZkbkLpRpQOuQo5YjWoBjM0Vf-XqOm8B7xtrvxSYHDD7Q4OatN2l-Iwg7i71lE3scUeD36x47st3nd0OThvtjrFx_D8mw_c0GT5KcniZlqq1SjhLyAk1b_zJsx8viRAhCU"; + + Y_UNIT_TEST(User) { + NTvmApi::TClientSettings s{ + .DiskCacheDir = GetCachePath(), + .SelfTvmId = OK_CLIENT, + .CheckServiceTickets = true, + }; + + auto l = MakeIntrusive<TLogger>(); + { + auto u = NTvmApi::TThreadedUpdater::Create(s, l); + UNIT_ASSERT_VALUES_EQUAL(TClientStatus::Ok, u->GetStatus().GetCode()); + UNIT_ASSERT_EXCEPTION(TUserTicketChecker::Check("kek", u->GetCachedUserContext()), TBrokenTvmClientSettings); + } + UNIT_ASSERT_C(l->Stream.Str().find("was successfully fetched") == TString::npos, l->Stream.Str()); + + s.CheckUserTicketsWithBbEnv = EBlackboxEnv::Prod; + { + auto u = NTvmApi::TThreadedUpdater::Create(s, l); + UNIT_ASSERT_VALUES_EQUAL(TClientStatus::Ok, u->GetStatus().GetCode()); + auto c = u->GetCachedUserContext({}); + UNIT_ASSERT(TUserTicketChecker::Check(PROD_TICKET, c)); + UNIT_ASSERT(!TUserTicketChecker::Check(TEST_TICKET, c)); + UNIT_ASSERT(!TUserTicketChecker::Check(PROD_YATEAM_TICKET, c)); + UNIT_ASSERT(!TUserTicketChecker::Check(TEST_YATEAM_TICKET, c)); + UNIT_ASSERT(!TUserTicketChecker::Check(STRESS_TICKET, c)); + + c = u->GetCachedUserContext(EBlackboxEnv::ProdYateam); + UNIT_ASSERT(!TUserTicketChecker::Check(PROD_TICKET, c)); + UNIT_ASSERT(!TUserTicketChecker::Check(TEST_TICKET, c)); + UNIT_ASSERT(TUserTicketChecker::Check(PROD_YATEAM_TICKET, c)); + UNIT_ASSERT(!TUserTicketChecker::Check(TEST_YATEAM_TICKET, c)); + UNIT_ASSERT(!TUserTicketChecker::Check(STRESS_TICKET, c)); + + UNIT_ASSERT_EXCEPTION(TUserTicketChecker::Check(PROD_TICKET, u->GetCachedUserContext(EBlackboxEnv::Stress)), TBrokenTvmClientSettings); + } + + s.CheckUserTicketsWithBbEnv = EBlackboxEnv::Test; + { + auto u = NTvmApi::TThreadedUpdater::Create(s, l); + UNIT_ASSERT_VALUES_EQUAL(TClientStatus::Ok, u->GetStatus().GetCode()); + auto c = u->GetCachedUserContext(); + + UNIT_ASSERT(!TUserTicketChecker::Check(PROD_TICKET, c)); + UNIT_ASSERT(TUserTicketChecker::Check(TEST_TICKET, c)); + UNIT_ASSERT(!TUserTicketChecker::Check(PROD_YATEAM_TICKET, c)); + UNIT_ASSERT(!TUserTicketChecker::Check(TEST_YATEAM_TICKET, c)); + UNIT_ASSERT(!TUserTicketChecker::Check(STRESS_TICKET, c)); + } + + s.CheckUserTicketsWithBbEnv = EBlackboxEnv::ProdYateam; + { + auto u = NTvmApi::TThreadedUpdater::Create(s, l); + UNIT_ASSERT_VALUES_EQUAL(TClientStatus::Ok, u->GetStatus().GetCode()); + auto c = u->GetCachedUserContext(); + + UNIT_ASSERT(!TUserTicketChecker::Check(PROD_TICKET, c)); + UNIT_ASSERT(!TUserTicketChecker::Check(TEST_TICKET, c)); + UNIT_ASSERT(TUserTicketChecker::Check(PROD_YATEAM_TICKET, c)); + UNIT_ASSERT(!TUserTicketChecker::Check(TEST_YATEAM_TICKET, c)); + UNIT_ASSERT(!TUserTicketChecker::Check(STRESS_TICKET, c)); + } + + s.CheckUserTicketsWithBbEnv = EBlackboxEnv::TestYateam; + { + auto u = NTvmApi::TThreadedUpdater::Create(s, l); + UNIT_ASSERT_VALUES_EQUAL(TClientStatus::Ok, u->GetStatus().GetCode()); + auto c = u->GetCachedUserContext(); + + UNIT_ASSERT(!TUserTicketChecker::Check(PROD_TICKET, c)); + UNIT_ASSERT(!TUserTicketChecker::Check(TEST_TICKET, c)); + UNIT_ASSERT(!TUserTicketChecker::Check(PROD_YATEAM_TICKET, c)); + UNIT_ASSERT(TUserTicketChecker::Check(TEST_YATEAM_TICKET, c)); + UNIT_ASSERT(!TUserTicketChecker::Check(STRESS_TICKET, c)); + } + + s.CheckUserTicketsWithBbEnv = EBlackboxEnv::Stress; + { + auto u = NTvmApi::TThreadedUpdater::Create(s, l); + UNIT_ASSERT_VALUES_EQUAL(TClientStatus::Ok, u->GetStatus().GetCode()); + auto c = u->GetCachedUserContext(); + + UNIT_ASSERT(TUserTicketChecker::Check(PROD_TICKET, c)); + UNIT_ASSERT(!TUserTicketChecker::Check(TEST_TICKET, c)); + UNIT_ASSERT(!TUserTicketChecker::Check(PROD_YATEAM_TICKET, c)); + UNIT_ASSERT(!TUserTicketChecker::Check(TEST_YATEAM_TICKET, c)); + UNIT_ASSERT(TUserTicketChecker::Check(STRESS_TICKET, c)); + } + } + + Y_UNIT_TEST(Service) { + NTvmApi::TClientSettings s{ + .DiskCacheDir = GetCachePath(), + .SelfTvmId = OK_CLIENT, + .CheckUserTicketsWithBbEnv = EBlackboxEnv::Stress, + }; + + auto l = MakeIntrusive<TLogger>(); + { + auto u = NTvmApi::TThreadedUpdater::Create(s, l); + UNIT_ASSERT_VALUES_EQUAL(TClientStatus::Ok, u->GetStatus().GetCode()); + UNIT_ASSERT_EXCEPTION(TServiceTicketChecker::Check(SRV_TICKET, u->GetCachedServiceContext()), TBrokenTvmClientSettings); + } + UNIT_ASSERT_C(l->Stream.Str().find("was successfully fetched") == TString::npos, l->Stream.Str()); + + s.CheckServiceTickets = true; + l = MakeIntrusive<TLogger>(); + { + auto u = NTvmApi::TThreadedUpdater::Create(s, l); + UNIT_ASSERT_VALUES_EQUAL(TClientStatus::Ok, u->GetStatus().GetCode()); + auto c = u->GetCachedServiceContext(); + + UNIT_ASSERT(TServiceTicketChecker::Check(SRV_TICKET, c)); + UNIT_ASSERT(!TServiceTicketChecker::Check(PROD_TICKET, c)); + } + UNIT_ASSERT_C(l->Stream.Str().find("was successfully fetched") == TString::npos, l->Stream.Str()); + + s.SelfTvmId = 17; + l = MakeIntrusive<TLogger>(); + { + auto u = NTvmApi::TThreadedUpdater::Create(s, l); + UNIT_ASSERT_VALUES_EQUAL(TClientStatus::Ok, u->GetStatus().GetCode()); + + auto c = u->GetCachedServiceContext(); + + UNIT_ASSERT(!TServiceTicketChecker::Check(SRV_TICKET, c)); + UNIT_ASSERT(!TServiceTicketChecker::Check(PROD_TICKET, c)); + } + UNIT_ASSERT_C(l->Stream.Str().find("was successfully fetched") == TString::npos, l->Stream.Str()); + } + + Y_UNIT_TEST(Tickets) { + NTvmApi::TClientSettings s{ + .DiskCacheDir = GetCachePath(), + .SelfTvmId = OK_CLIENT, + .Secret = (TStringBuf) "qwerty", + .FetchServiceTicketsForDstsWithAliases = {{"blackbox", 19}}, + }; + + auto l = MakeIntrusive<TLogger>(); + { + auto u = NTvmApi::TThreadedUpdater::Create(s, l); + UNIT_ASSERT_VALUES_EQUAL(TClientStatus::Ok, u->GetStatus().GetCode()); + auto c = u->GetCachedServiceTickets(); + UNIT_ASSERT_VALUES_EQUAL("3:serv:CBAQ__________9_IgYIKhCUkQY:CX", TServiceTicketGetter::GetTicket("blackbox", c)); + UNIT_ASSERT_EXCEPTION_CONTAINS(TServiceTicketGetter::GetTicket("blackbox2", c), + TBrokenTvmClientSettings, + "Destination 'blackbox2' was not specified in settings. Check your settings (if you use Qloud/YP/tvmtool - check it's settings)"); + } + UNIT_ASSERT_C(l->Stream.Str().find("was successfully fetched") == TString::npos, l->Stream.Str()); + } + + Y_UNIT_TEST(ErrorForDst) { + TAsyncUpdaterPtr u = new TMockedUpdater(); + auto c = u->GetCachedServiceTickets(); + + UNIT_ASSERT_VALUES_EQUAL(TMockedUpdater::TSettings::CreateDeafult().Backends.at(0).Value, + TServiceTicketGetter::GetTicket("my_dest", c)); + UNIT_ASSERT_VALUES_EQUAL(TMockedUpdater::TSettings::CreateDeafult().Backends.at(0).Value, + TServiceTicketGetter::GetTicket(42, c)); + UNIT_ASSERT_EXCEPTION_CONTAINS(TServiceTicketGetter::GetTicket("my_bad_dest", c), + TMissingServiceTicket, + "Failed to get ticket for 'my_bad_dest': Dst is not found"); + UNIT_ASSERT_EXCEPTION_CONTAINS(TServiceTicketGetter::GetTicket(43, c), + TMissingServiceTicket, + "Failed to get ticket for '43': Dst is not found"); + } +} diff --git a/library/cpp/tvmauth/client/ut/client_status_ut.cpp b/library/cpp/tvmauth/client/ut/client_status_ut.cpp new file mode 100644 index 0000000000..a1c3ae74ce --- /dev/null +++ b/library/cpp/tvmauth/client/ut/client_status_ut.cpp @@ -0,0 +1,18 @@ +#include <library/cpp/tvmauth/client/client_status.h> + +#include <library/cpp/testing/unittest/registar.h> + +using namespace NTvmAuth; + +Y_UNIT_TEST_SUITE(ClientStatus) { + Y_UNIT_TEST(Common) { + UNIT_ASSERT_VALUES_EQUAL(TClientStatus::Ok, TClientStatus().GetCode()); + UNIT_ASSERT_VALUES_EQUAL("", TClientStatus().GetLastError()); + + UNIT_ASSERT_VALUES_EQUAL(TClientStatus::Warning, TClientStatus(TClientStatus::Warning, "kek")); + UNIT_ASSERT_VALUES_EQUAL("kek", + TClientStatus(TClientStatus::Warning, "kek").GetLastError()); + UNIT_ASSERT_VALUES_EQUAL("2;TvmClient: kek\n", + TClientStatus(TClientStatus::Error, "kek").CreateJugglerMessage()); + } +} diff --git a/library/cpp/tvmauth/client/ut/common.h b/library/cpp/tvmauth/client/ut/common.h new file mode 100644 index 0000000000..0aee09aefc --- /dev/null +++ b/library/cpp/tvmauth/client/ut/common.h @@ -0,0 +1,240 @@ +#pragma once + +#include <library/cpp/tvmauth/client/logger.h> +#include <library/cpp/tvmauth/client/misc/disk_cache.h> +#include <library/cpp/tvmauth/client/misc/roles/entities_index.h> + +#include <library/cpp/tvmauth/unittest.h> + +#include <library/cpp/cgiparam/cgiparam.h> +#include <library/cpp/testing/mock_server/server.h> +#include <library/cpp/testing/unittest/env.h> +#include <library/cpp/testing/unittest/tests_data.h> + +#include <util/stream/str.h> +#include <util/system/fs.h> + +class TLogger: public NTvmAuth::ILogger { +public: + void Log(int lvl, const TString& msg) override { + Cout << TInstant::Now() << " lvl=" << lvl << " msg: " << msg << "\n"; + Stream << lvl << ": " << msg << Endl; + } + + TStringStream Stream; +}; + +static inline TString GetFilePath(const char* name) { + return ArcadiaSourceRoot() + "/library/cpp/tvmauth/client/ut/files/" + name; +} + +static inline TString GetCachePath(const TString& dir = {}) { + if (dir) { + Y_ENSURE(NFs::MakeDirectoryRecursive("./" + dir)); + } + + auto wr = [](const TString& p, const TStringBuf body) { + NTvmAuth::TDiskWriter w(p); + Y_ENSURE(w.Write(body, TInstant::ParseIso8601("2050-01-01T00:00:00.000000Z"))); + }; + wr("./" + dir + "/public_keys", NTvmAuth::NUnittest::TVMKNIFE_PUBLIC_KEYS); + wr("./" + dir + "/service_tickets", + R"({ + "19" : { "ticket" : "3:serv:CBAQ__________9_IgYIKhCUkQY:CX"}, + "213" : { "ticket" : "service_ticket_2"}, + "234" : { "error" : "Dst is not found" }, + "185" : { "ticket" : "service_ticket_3"} +} 100500)"); + + return "./" + dir; +} + +static const TString AUTH_TOKEN = "strong_token"; +static const TString META = R"( +{ +"bb_env" : "ProdYaTeam", +"tenants" : [ + { + "self": { + "alias" : "me", + "client_id": 100500 + }, + "dsts" : [ + { + "alias" : "bbox", + "client_id": 242 + }, + { + "alias" : "pass_likers", + "client_id": 11 + } + ] + }, + { + "self": { + "alias" : "tenant_with_roles", + "client_id": 100500 + }, + "idm_slug": "some_slug", + "dsts" : [] + }, + { + "self": { + "alias" : "push-client", + "client_id": 100501 + }, + "dsts" : [ + { + "alias" : "pass_likers", + "client_id": 100502 + } + ] + }, + { + "self": { + "alias" : "multi_names_for_dst", + "client_id": 100599 + }, + "dsts" : [ + { + "alias" : "pass_likers", + "client_id": 100502 + }, + { + "alias" : "pass_haters", + "client_id": 100502 + } + ] + }, + { + "self": { + "alias" : "something_else", + "client_id": 100503 + }, + "dsts" : [ + ] + } +] +})"; + +static const TString TICKETS_ME = + R"({ + "pass_likers": { + "ticket": "3:serv:CBAQ__________9_IgYIlJEGEAs:T-apeMNWFc_vHPQ3iLaZv9NjG-hf5-i23O4AhRu1M68ryN3FU5qvyqTSSiPbtJdFP6EE41QQBzEs59dHn9DRkqQNwwKf1is00Oewwj2XKO0uHukuzd9XxZnro7MfjPswsjWufxX28rmJtlfSXwAtyKt8TI5yKJnMeBPQ0m5R3k8", + "tvm_id": 11 + }, + "bbox": { + "ticket": "3:serv:CBAQ__________9_IgcIlJEGEPIB:N7luw0_rVmBosTTI130jwDbQd0-cMmqJeEl0ma4ZlIo_mHXjBzpOuMQ3A9YagbmOBOt8TZ_gzGvVSegWZkEeB24gM22acw0w-RcHaQKrzSOA5Zq8WLNIC8QUa4_WGTlAsb7R7eC4KTAGgouIquNAgMBdTuGOuZHnMLvZyLnOMKc", + "tvm_id": 242 + } + })"; + +static const TString SERVICE_TICKET_PC = "3:serv:CBAQ__________9_IggIlpEGEJaRBg:BAxaQJCdK4eFuJ6i_egqPwvJgWtlh0enDQRPr84Nx2phZ_8QtxKAUCwEa7KOU_jVvIBQIC5-ETTl2vjBt7UyygF8frdK4ab6zJoWj4n07np6vbmWd385l8KvzztLt4QkBrPiE7U46dK3pL0U8tfBkSXE8rvUIsl3RvvgSNH2J3c"; +static const TString TICKETS_PC = + R"({ + "pass_likers": { + "ticket": "3:serv:CBAQ__________9_IggIlpEGEJaRBg:BAxaQJCdK4eFuJ6i_egqPwvJgWtlh0enDQRPr84Nx2phZ_8QtxKAUCwEa7KOU_jVvIBQIC5-ETTl2vjBt7UyygF8frdK4ab6zJoWj4n07np6vbmWd385l8KvzztLt4QkBrPiE7U46dK3pL0U8tfBkSXE8rvUIsl3RvvgSNH2J3c", + "tvm_id": 100502 + } + })"; + +static const TString TICKETS_MANY_DSTS = + R"({ + "pass_likers": { + "ticket": "3:serv:CBAQ__________9_IggI95EGEJaRBg:D0MOLDhKQyI-OhC0ON9gYukz2hOctUipu1yXsvkw6NRuLhcBfvGayyUqF4ILrqepjz9GtPWIR_wO6oLSW35Z0YaFn60QWp5tG6IcAnr80lm_OnLHJt4kmEoLtGg1V0aWBT0YyouzGB2-QFNOVO86G7sYzU8FC6-V3Iyc4X7XTNc", + "tvm_id": 100502 + }, + "who_are_you??": { + "ticket": "kek", + "tvm_id": 100503 + }, + "pass_haters": { + "ticket": "3:serv:CBAQ__________9_IggI95EGEJaRBg:D0MOLDhKQyI-OhC0ON9gYukz2hOctUipu1yXsvkw6NRuLhcBfvGayyUqF4ILrqepjz9GtPWIR_wO6oLSW35Z0YaFn60QWp5tG6IcAnr80lm_OnLHJt4kmEoLtGg1V0aWBT0YyouzGB2-QFNOVO86G7sYzU8FC6-V3Iyc4X7XTNc", + "tvm_id": 100502 + } + })"; + +static const TString TICKETS_SE = R"({})"; + +static const TInstant BIRTHTIME = TInstant::Seconds(14380887840); +class TTvmTool: public TRequestReplier { +public: + TString Meta; + HttpCodes Code; + TInstant Birthtime; + + TTvmTool() + : Meta(META) + , Code(HTTP_OK) + , Birthtime(BIRTHTIME) + { + } + + bool DoReply(const TReplyParams& params) override { + const TParsedHttpFull http(params.Input.FirstLine()); + if (http.Path == "/tvm/ping") { + THttpResponse resp(HTTP_OK); + resp.SetContent("OK"); + resp.OutTo(params.Output); + return true; + } + + auto it = std::find_if(params.Input.Headers().begin(), + params.Input.Headers().end(), + [](const THttpInputHeader& h) { return h.Name() == "Authorization"; }); + if (it == params.Input.Headers().end() || it->Value() != AUTH_TOKEN) { + THttpResponse resp(HTTP_UNAUTHORIZED); + resp.SetContent("pong"); + resp.OutTo(params.Output); + return true; + } + + THttpResponse resp(Code); + if (http.Path == "/tvm/keys") { + resp.SetContent(NTvmAuth::NUnittest::TVMKNIFE_PUBLIC_KEYS); + } else if (http.Path == "/tvm/tickets") { + TCgiParameters cg; + cg.ScanAddAll(http.Cgi); + if (cg.Get("src") == "100500") { + resp.SetContent(TICKETS_ME); + } else if (cg.Get("src") == "100501") { + resp.SetContent(TICKETS_PC); + } else if (cg.Get("src") == "100599") { + resp.SetContent(TICKETS_MANY_DSTS); + } + } else if (http.Path == "/tvm/private_api/__meta__") { + resp.SetContent(Meta); + } + resp.AddHeader("X-Ya-Tvmtool-Data-Birthtime", IntToString<10>(Birthtime.Seconds())); + resp.OutTo(params.Output); + + return true; + } +}; + +static inline NTvmAuth::NRoles::TEntitiesIndex CreateEntitiesIndex() { + using namespace NTvmAuth::NRoles; + + TEntitiesIndex index( + { + std::make_shared<TEntity>(TEntity{ + {"key#1", "value#11"}, + }), + std::make_shared<TEntity>(TEntity{ + {"key#1", "value#11"}, + {"key#2", "value#22"}, + {"key#3", "value#33"}, + }), + std::make_shared<TEntity>(TEntity{ + {"key#1", "value#11"}, + {"key#2", "value#23"}, + {"key#3", "value#33"}, + }), + std::make_shared<TEntity>(TEntity{ + {"key#1", "value#13"}, + {"key#3", "value#33"}, + }), + }); + + return index; +} diff --git a/library/cpp/tvmauth/client/ut/default_uid_checker_ut.cpp b/library/cpp/tvmauth/client/ut/default_uid_checker_ut.cpp new file mode 100644 index 0000000000..8c48c495c4 --- /dev/null +++ b/library/cpp/tvmauth/client/ut/default_uid_checker_ut.cpp @@ -0,0 +1,47 @@ +#include "common.h" + +#include <library/cpp/tvmauth/client/mocked_updater.h> +#include <library/cpp/tvmauth/client/misc/default_uid_checker.h> +#include <library/cpp/tvmauth/client/misc/api/threaded_updater.h> + +#include <library/cpp/tvmauth/type.h> +#include <library/cpp/tvmauth/unittest.h> + +#include <library/cpp/testing/unittest/registar.h> + +using namespace NTvmAuth; + +Y_UNIT_TEST_SUITE(DefaultUidChecker) { + Y_UNIT_TEST(Check) { + NRoles::TRolesPtr roles = std::make_shared<NRoles::TRoles>( + NRoles::TRoles::TMeta{}, + NRoles::TRoles::TTvmConsumers{}, + NRoles::TRoles::TUserConsumers{ + {12345, std::make_shared<NRoles::TConsumerRoles>( + THashMap<TString, NRoles::TEntitiesPtr>())}, + }, + std::make_shared<TString>()); + + TAsyncUpdaterPtr u = new TMockedUpdater({.Roles = roles}); + auto r = u->GetRoles(); + + UNIT_ASSERT_EXCEPTION_CONTAINS( + TDefaultUidChecker::Check(NUnittest::CreateUserTicket(ETicketStatus::Expired, 12345, {}), r), + TIllegalUsage, + "User ticket must be valid"); + + UNIT_ASSERT_EXCEPTION_CONTAINS( + TDefaultUidChecker::Check(NUnittest::CreateUserTicket(ETicketStatus::Ok, 12345, {}, {}, EBlackboxEnv::Test), r), + TIllegalUsage, + "User ticket must be from ProdYateam, got from Test"); + + TCheckedUserTicket ticket; + UNIT_ASSERT_NO_EXCEPTION( + ticket = TDefaultUidChecker::Check(NUnittest::CreateUserTicket(ETicketStatus::Ok, 12345, {}, {}, EBlackboxEnv::ProdYateam), r)); + UNIT_ASSERT_VALUES_EQUAL(ETicketStatus::Ok, ticket.GetStatus()); + + UNIT_ASSERT_NO_EXCEPTION( + ticket = TDefaultUidChecker::Check(NUnittest::CreateUserTicket(ETicketStatus::Ok, 9999, {}, {}, EBlackboxEnv::ProdYateam), r)); + UNIT_ASSERT_VALUES_EQUAL(ETicketStatus::NoRoles, ticket.GetStatus()); + } +} diff --git a/library/cpp/tvmauth/client/ut/disk_cache_ut.cpp b/library/cpp/tvmauth/client/ut/disk_cache_ut.cpp new file mode 100644 index 0000000000..7dd851c9b3 --- /dev/null +++ b/library/cpp/tvmauth/client/ut/disk_cache_ut.cpp @@ -0,0 +1,204 @@ +#include "common.h" + +#include <library/cpp/tvmauth/client/logger.h> +#include <library/cpp/tvmauth/client/misc/disk_cache.h> + +#include <library/cpp/tvmauth/src/utils.h> + +#include <library/cpp/testing/unittest/registar.h> +#include <library/cpp/testing/unittest/tests_data.h> + +#include <util/stream/file.h> +#include <util/system/fs.h> +#include <util/system/sysstat.h> + +#include <thread> + +using namespace NTvmAuth; + +Y_UNIT_TEST_SUITE(ClientDisk) { + Y_UNIT_TEST(Hash) { + TString hash = TDiskReader::GetHash("asd"); + UNIT_ASSERT(hash); + UNIT_ASSERT_VALUES_EQUAL(32, hash.size()); + UNIT_ASSERT_VALUES_EQUAL("Zj5_qYg31bPlqjBW76z8IV0rCsHmv-iN-McV6ybS1-g", NUtils::Bin2base64url(hash)); + } + + Y_UNIT_TEST(Timestamp) { + time_t t = 100500; + + TString s = TDiskWriter::WriteTimestamp(t); + UNIT_ASSERT_VALUES_EQUAL("lIgBAAAAAAA", NUtils::Bin2base64url(s)); + UNIT_ASSERT_VALUES_EQUAL(t, TDiskReader::GetTimestamp(s)); + + t = 123123123213089; + s = TDiskWriter::WriteTimestamp(t); + UNIT_ASSERT_VALUES_EQUAL("IdMF1vpvAAA", NUtils::Bin2base64url(s)); + UNIT_ASSERT_VALUES_EQUAL(t, TDiskReader::GetTimestamp(s)); + + t = time(nullptr); + s = TDiskWriter::WriteTimestamp(t); + UNIT_ASSERT_VALUES_EQUAL(t, TDiskReader::GetTimestamp(s)); + } + + const TInstant TIME = TInstant::Seconds(100500); + const TString DATA = "oiweuhn \n vw3ut hweoi uhgewproritjhwequtherwoiughfdsv 8ty34q01u 34 1=3"; + + Y_UNIT_TEST(ParseData_Ok) { + TLogger l; + + const TInstant time = TInstant::Seconds(1523446554789); + + TString toFile = TDiskWriter::PrepareData(time, DATA); + UNIT_ASSERT_VALUES_EQUAL(113, toFile.size()); + UNIT_ASSERT_VALUES_EQUAL("T8BnRIMoC6mlMXexPg9cV5jYxeFtgDWk97JTajHDunCloH20YgEAAG9pd2V1aG4gCiB2dzN1dCBod2VvaSB1aGdld3Byb3JpdGpod2VxdXRoZXJ3b2l1Z2hmZHN2IDh0eTM0cTAxdSAgIDM0ICAxPTM", + NUtils::Bin2base64url(toFile)); + + TDiskReader r("qwerty", &l); + UNIT_ASSERT(r.ParseData(toFile)); + UNIT_ASSERT_VALUES_EQUAL(DATA, r.Data()); + UNIT_ASSERT_VALUES_EQUAL(time, r.Time()); + UNIT_ASSERT_VALUES_EQUAL("6: File 'qwerty' was successfully read\n", + l.Stream.Str()); + } + + Y_UNIT_TEST(ParseData_SmallFile) { + TLogger l; + + TString toFile = TDiskWriter::PrepareData(TIME, DATA); + TDiskReader r("qwerty", &l); + UNIT_ASSERT(!r.ParseData(toFile.substr(0, 17))); + UNIT_ASSERT_VALUES_EQUAL("4: File 'qwerty' is too small\n", + l.Stream.Str()); + } + + Y_UNIT_TEST(ParseData_Changed) { + TLogger l; + + TString toFile = TDiskWriter::PrepareData(TIME, DATA); + toFile[17] = toFile[17] + 1; + TDiskReader r("qwerty", &l); + UNIT_ASSERT(!r.ParseData(toFile)); + UNIT_ASSERT_VALUES_EQUAL("4: Content of 'qwerty' was incorrectly changed\n", + l.Stream.Str()); + } + + Y_UNIT_TEST(Read_Ok) { + TLogger l; + + TDiskReader r(GetFilePath("ok.cache"), &l); + UNIT_ASSERT(r.Read()); + UNIT_ASSERT_VALUES_EQUAL(DATA, r.Data()); + UNIT_ASSERT_VALUES_EQUAL(TIME, r.Time()); + UNIT_ASSERT_C(l.Stream.Str().find("was successfully read") != TString::npos, l.Stream.Str()); + } + + Y_UNIT_TEST(Read_NoFile) { + TLogger l; + + TDiskReader r("missing", &l); + UNIT_ASSERT(!r.Read()); + UNIT_ASSERT_VALUES_EQUAL("7: File 'missing' does not exist\n", + l.Stream.Str()); + } + +#ifdef _unix_ + Y_UNIT_TEST(Read_NoPermitions) { + TLogger l; + + const TString path = GetWorkPath() + "/123"; + { + TFileOutput output(path); + } + Chmod(path.data(), S_IWUSR); + + TDiskReader r(path, &l); + UNIT_ASSERT(!r.Read()); + UNIT_ASSERT_C(l.Stream.Str().find("Permission denied") != TString::npos, l.Stream.Str()); + + Chmod(path.data(), S_IRWXU); + NFs::Remove(path); + } +#endif + + Y_UNIT_TEST(Write_Ok) { + TLogger l; + + const TString path = "./tmp_file"; + TDiskWriter w(path, &l); + UNIT_ASSERT_C(w.Write(DATA), l.Stream.Str()); + UNIT_ASSERT_C(l.Stream.Str().find("was successfully written") != TString::npos, l.Stream.Str()); + l.Stream.Clear(); + + TDiskReader r(path, &l); + UNIT_ASSERT_C(r.Read(), l.Stream.Str()); + UNIT_ASSERT_VALUES_EQUAL(DATA, r.Data()); + UNIT_ASSERT(TInstant::Now() - r.Time() < TDuration::Minutes(5)); + UNIT_ASSERT_C(l.Stream.Str().find("was successfully read") != TString::npos, l.Stream.Str()); + + NFs::Remove(path); + } + + Y_UNIT_TEST(Write_NoPermitions) { + TLogger l; + + TDiskWriter w("/some_file", &l); + UNIT_ASSERT(!w.Write(DATA)); + UNIT_ASSERT_C(l.Stream.Str().Contains("3: Failed to write '/some_file': ("), l.Stream.Str()); + UNIT_ASSERT_C(l.Stream.Str().Contains("denied"), l.Stream.Str()); + } + + Y_UNIT_TEST(race) { + const TString path = "./tmp_file"; + const TString data = "ejufhsadkjfvbhsaoicnaofssdahfasdfhasdofdsaf"; + NFs::Remove(path); + + std::atomic<bool> fail = false; + std::vector<std::thread> thrs; + for (size_t idx = 0; idx < 16; ++idx) { + thrs.push_back(std::thread([&fail, data, path]() { + TDiskWriter w(path); + for (size_t k = 0; k < 1000; ++k) { + if (!w.Write(data)) { + fail = true; + } + } + })); + } + for (std::thread& t : thrs) { + t.join(); + } + thrs.clear(); + UNIT_ASSERT(fail); + { + TDiskWriter w(path); + UNIT_ASSERT(w.Write(data)); // checks unlocked flock + } + + fail = false; + + for (size_t idx = 0; idx < 4; ++idx) { + thrs.push_back(std::thread([&fail, data, path]() { + TLogger l; + TDiskReader r(path, &l); + for (size_t k = 0; k < 100; ++k) { + if (!r.Read()) { + Cerr << l.Stream.Str() << Flush; + fail = true; + return; + } + if (r.Data() != data) { + Cerr << (TStringBuilder() << "'" << data << "' vs '" << r.Data() << "'" << Endl) << Flush; + fail = true; + return; + } + } + })); + } + for (std::thread& t : thrs) { + t.join(); + } + thrs.clear(); + UNIT_ASSERT(!fail); + } +} diff --git a/library/cpp/tvmauth/client/ut/exponential_backoff_ut.cpp b/library/cpp/tvmauth/client/ut/exponential_backoff_ut.cpp new file mode 100644 index 0000000000..3dcbe6ad49 --- /dev/null +++ b/library/cpp/tvmauth/client/ut/exponential_backoff_ut.cpp @@ -0,0 +1,44 @@ +#include <library/cpp/tvmauth/client/misc/exponential_backoff.h> + +#include <library/cpp/testing/unittest/registar.h> + +#include <thread> + +using namespace NTvmAuth; + +Y_UNIT_TEST_SUITE(PasspUtilsExpBackoff) { + Y_UNIT_TEST(common) { + TExponentialBackoff b({TDuration::Seconds(1), TDuration::Seconds(60), 2, 0.01}); + + UNIT_ASSERT_VALUES_EQUAL(TDuration::Seconds(1), b.GetCurrentValue()); + + TDuration dur = b.GetCurrentValue(); + for (size_t idx = 0; idx < 6; ++idx) { + TDuration newValue = b.Increase(); + UNIT_ASSERT_LT(dur, newValue); + dur = newValue; + } + + UNIT_ASSERT_LT(TDuration::Seconds(60) - TDuration::Seconds(3), dur); + UNIT_ASSERT_LT(dur, TDuration::Seconds(60) + TDuration::Seconds(3)); + } + + Y_UNIT_TEST(sleep) { + TExponentialBackoff b({TDuration::Seconds(60), TDuration::Seconds(600), 2, 0.01}); + + const TInstant start = TInstant::Now(); + + TAutoEvent started; + std::thread t([&b, &started]() { + started.Signal(); + b.Sleep(); + }); + + started.WaitT(TDuration::Seconds(30)); + b.Interrupt(); + t.join(); + TDuration dur = TInstant::Now() - start; + + UNIT_ASSERT_LT(dur, TDuration::Seconds(60)); + } +} diff --git a/library/cpp/tvmauth/client/ut/facade_ut.cpp b/library/cpp/tvmauth/client/ut/facade_ut.cpp new file mode 100644 index 0000000000..0a3013ecea --- /dev/null +++ b/library/cpp/tvmauth/client/ut/facade_ut.cpp @@ -0,0 +1,205 @@ +#include "common.h" + +#include <library/cpp/tvmauth/client/facade.h> +#include <library/cpp/tvmauth/client/mocked_updater.h> + +#include <library/cpp/testing/unittest/registar.h> + +#include <util/generic/vector.h> + +using namespace NTvmAuth; + +Y_UNIT_TEST_SUITE(ClientFacade) { + static const TTvmId OK_CLIENT = 100500; + static const TString SRV_TICKET_123 = "3:serv:CBAQ__________9_IgYIexCUkQY:GioCM49Ob6_f80y6FY0XBVN4hLXuMlFeyMvIMiDuQnZkbkLpRpQOuQo5YjWoBjM0Vf-XqOm8B7xtrvxSYHDD7Q4OatN2l-Iwg7i71lE3scUeD36x47st3nd0OThvtjrFx_D8mw_c0GT5KcniZlqq1SjhLyAk1b_zJsx8viRAhCU"; + static const TString SRV_TICKET_456 = "3:serv:CBAQ__________9_IgcIyAMQlJEG:VrnqRhpoiDnJeAQbySJluJ1moQ5Kemic99iWzOrHLGfuh7iTw_xMT7KewRAmZMUwDKzE6otj7V86Xsnxbv5xZl8746wbvNcyUXu-nGWmbByZjO7xpSIcY07sISqEhP9n9C_yMSvqDP7ho_PRIfpGCDMXxKlFZ_BhBLLp0kHEvw4"; + static const TString PROD_TICKET = "3:user:CAsQ__________9_Gg4KAgh7EHsg0oXYzAQoAA:N8PvrDNLh-5JywinxJntLeQGDEHBUxfzjuvB8-_BEUv1x9CALU7do8irDlDYVeVVDr4AIpR087YPZVzWPAqmnBuRJS0tJXekmDDvrivLnbRrzY4IUXZ_fImB0fJhTyVetKv6RD11bGqnAJeDpIukBwPTbJc_EMvKDt8V490CJFw"; + static const TString TEST_TICKET = "3:user:CA0Q__________9_Gg4KAgh7EHsg0oXYzAQoAQ:FSADps3wNGm92Vyb1E9IVq5M6ZygdGdt1vafWWEhfDDeCLoVA-sJesxMl2pGW4OxJ8J1r_MfpG3ZoBk8rLVMHUFrPa6HheTbeXFAWl8quEniauXvKQe4VyrpA1SPgtRoFqi5upSDIJzEAe1YRJjq1EClQ_slMt8R0kA_JjKUX54"; + + TTvmClient GetClient(const NTvmApi::TClientSettings& s) { + auto l = MakeIntrusive<TLogger>(); + TTvmClient f(s, l); + UNIT_ASSERT_VALUES_EQUAL(TClientStatus::Ok, f.GetStatus()); + Sleep(TDuration::MilliSeconds(300)); + TString logs = l->Stream.Str(); + UNIT_ASSERT_C(logs.find("was successfully read") != TString::npos, logs); + UNIT_ASSERT_C(logs.find("was successfully fetched") == TString::npos, logs); + return f; + } + + Y_UNIT_TEST(Service) { + NTvmApi::TClientSettings s{ + .DiskCacheDir = GetCachePath(), + .SelfTvmId = OK_CLIENT, + .CheckServiceTickets = true, + }; + TTvmClient f = GetClient(s); + + UNIT_ASSERT_VALUES_EQUAL(TInstant::Seconds(2524608000), f.GetUpdateTimeOfPublicKeys()); + UNIT_ASSERT_VALUES_EQUAL(TInstant(), f.GetUpdateTimeOfServiceTickets()); + UNIT_ASSERT_VALUES_EQUAL(TInstant::Seconds(2525126400), f.GetInvalidationTimeOfPublicKeys()); + UNIT_ASSERT_VALUES_EQUAL(TInstant(), f.GetInvalidationTimeOfServiceTickets()); + + UNIT_ASSERT(f.CheckServiceTicket(SRV_TICKET_123)); + UNIT_ASSERT_EXCEPTION(f.CheckUserTicket(PROD_TICKET), yexception); + UNIT_ASSERT_EXCEPTION(f.CheckUserTicket(TEST_TICKET), yexception); + } + + Y_UNIT_TEST(User) { + NTvmApi::TClientSettings s{ + .DiskCacheDir = GetCachePath(), + .CheckUserTicketsWithBbEnv = EBlackboxEnv::Prod, + }; + + TTvmClient f = GetClient(s); + UNIT_ASSERT_EXCEPTION(f.CheckServiceTicket(SRV_TICKET_123), yexception); + UNIT_ASSERT(f.CheckUserTicket(PROD_TICKET)); + UNIT_ASSERT(!f.CheckUserTicket(TEST_TICKET)); + } + + Y_UNIT_TEST(Ctors) { + NTvmApi::TClientSettings s{ + .DiskCacheDir = GetCachePath(), + .CheckUserTicketsWithBbEnv = EBlackboxEnv::Prod, + }; + + TTvmClient f = GetClient(s); + f = GetClient(s); + + TVector<TTvmClient> v; + v.push_back(std::move(f)); + v.front() = std::move(*v.begin()); + } + + Y_UNIT_TEST(Tickets) { + NTvmApi::TClientSettings s{ + .DiskCacheDir = GetCachePath(), + .SelfTvmId = OK_CLIENT, + .Secret = (TStringBuf) "qwerty", + .FetchServiceTicketsForDstsWithAliases = {{"blackbox", 19}}, + }; + TTvmClient f = GetClient(s); + + UNIT_ASSERT_VALUES_EQUAL(TInstant(), f.GetUpdateTimeOfPublicKeys()); + UNIT_ASSERT_VALUES_EQUAL(TInstant::ParseIso8601("2050-01-01T00:00:00.000000Z"), f.GetUpdateTimeOfServiceTickets()); + UNIT_ASSERT_VALUES_EQUAL(TInstant(), f.GetInvalidationTimeOfPublicKeys()); + UNIT_ASSERT_VALUES_EQUAL(TInstant::Seconds(std::numeric_limits<size_t>::max()), f.GetInvalidationTimeOfServiceTickets()); + + UNIT_ASSERT_VALUES_EQUAL("3:serv:CBAQ__________9_IgYIKhCUkQY:CX", f.GetServiceTicketFor("blackbox")); + UNIT_ASSERT_VALUES_EQUAL("3:serv:CBAQ__________9_IgYIKhCUkQY:CX", f.GetServiceTicketFor(19)); + UNIT_ASSERT_EXCEPTION_CONTAINS(f.GetServiceTicketFor("blackbox2"), + TBrokenTvmClientSettings, + "Destination 'blackbox2' was not specified in settings. Check your settings (if you use Qloud/YP/tvmtool - check it's settings)"); + UNIT_ASSERT_EXCEPTION_CONTAINS(f.GetServiceTicketFor(20), + TBrokenTvmClientSettings, + "Destination '20' was not specified in settings. Check your settings (if you use Qloud/YP/tvmtool - check it's settings)"); + } + + Y_UNIT_TEST(Tool) { + TPortManager pm; + ui16 port = pm.GetPort(80); + NMock::TMockServer server(port, []() { return new TTvmTool; }); + + NTvmTool::TClientSettings s("push-client"); + s.SetPort(port); + s.SetAuthToken(AUTH_TOKEN); + auto l = MakeIntrusive<TLogger>(); + { + TTvmClient f(s, l); + + UNIT_ASSERT_VALUES_EQUAL(TInstant::Seconds(14380887840), f.GetUpdateTimeOfPublicKeys()); + UNIT_ASSERT_VALUES_EQUAL(TInstant::Seconds(14380887840), f.GetUpdateTimeOfServiceTickets()); + UNIT_ASSERT_VALUES_EQUAL(TInstant::Seconds(14381406240), f.GetInvalidationTimeOfPublicKeys()); + UNIT_ASSERT_VALUES_EQUAL(TInstant::Seconds(std::numeric_limits<time_t>::max()), f.GetInvalidationTimeOfServiceTickets()); + + UNIT_ASSERT_VALUES_EQUAL(SERVICE_TICKET_PC, f.GetServiceTicketFor("pass_likers")); + UNIT_ASSERT_VALUES_EQUAL(SERVICE_TICKET_PC, f.GetServiceTicketFor(100502)); + UNIT_ASSERT_EXCEPTION_CONTAINS(f.GetServiceTicketFor("blackbox"), + TBrokenTvmClientSettings, + "Destination 'blackbox' was not specified in settings. Check your settings (if you use Qloud/YP/tvmtool - check it's settings)"); + UNIT_ASSERT_EXCEPTION_CONTAINS(f.GetServiceTicketFor(242), + TBrokenTvmClientSettings, + "Destination '242' was not specified in settings. Check your settings (if you use Qloud/YP/tvmtool - check it's settings)"); + } + + UNIT_ASSERT_VALUES_EQUAL( + TStringBuilder() + << "7: Meta info fetched from localhost:" << port << "\n" + << "6: Meta: self_tvm_id=100501, bb_env=ProdYateam, idm_slug=<NULL>, dsts=[(pass_likers:100502)]\n" + << "7: Tickets fetched from tvmtool: 2425-09-17T11:04:00.000000Z\n" + << "7: Public keys fetched from tvmtool: 2425-09-17T11:04:00.000000Z\n" + << "7: Thread-worker started\n" + << "7: Thread-worker stopped\n", + l->Stream.Str()); + } + + Y_UNIT_TEST(CheckRoles) { + { // roles not configured + TTvmClient f(new TMockedUpdater(TMockedUpdater::TSettings{ + .SelfTvmId = OK_CLIENT, + })); + + UNIT_ASSERT_VALUES_EQUAL(ETicketStatus::Ok, + f.CheckServiceTicket(SRV_TICKET_123).GetStatus()); + UNIT_ASSERT_VALUES_EQUAL(ETicketStatus::Ok, + f.CheckServiceTicket(SRV_TICKET_456).GetStatus()); + UNIT_ASSERT_VALUES_EQUAL(ETicketStatus::Malformed, + f.CheckServiceTicket("asdfg").GetStatus()); + } + + { // roles configured + NRoles::TRolesPtr roles = std::make_shared<NRoles::TRoles>( + NRoles::TRoles::TMeta{}, + NRoles::TRoles::TTvmConsumers{ + {123, std::make_shared<NRoles::TConsumerRoles>( + THashMap<TString, NRoles::TEntitiesPtr>())}, + }, + NRoles::TRoles::TUserConsumers{}, + std::make_shared<TString>()); + TTvmClient f(new TMockedUpdater(TMockedUpdater::TSettings{ + .SelfTvmId = OK_CLIENT, + .Roles = roles, + })); + + UNIT_ASSERT_VALUES_EQUAL(ETicketStatus::Ok, + f.CheckServiceTicket(SRV_TICKET_123).GetStatus()); + UNIT_ASSERT_VALUES_EQUAL(ETicketStatus::NoRoles, + f.CheckServiceTicket(SRV_TICKET_456).GetStatus()); + UNIT_ASSERT_VALUES_EQUAL(ETicketStatus::Malformed, + f.CheckServiceTicket("asdfg").GetStatus()); + } + } + + Y_UNIT_TEST(CheckDst) { + { + TTvmClient f(new TMockedUpdater(TMockedUpdater::TSettings{ + .SelfTvmId = OK_CLIENT, + })); + UNIT_ASSERT_VALUES_EQUAL(ETicketStatus::Ok, f.CheckServiceTicket(SRV_TICKET_123).GetStatus()); + UNIT_ASSERT_VALUES_EQUAL(ETicketStatus::Ok, f.CheckServiceTicket(SRV_TICKET_456).GetStatus()); + UNIT_ASSERT_VALUES_EQUAL(ETicketStatus::Malformed, f.CheckServiceTicket("asdfg").GetStatus()); + } + TTvmId NOT_OK_CLIENT = 3333; + { + TTvmClient f(new TMockedUpdater(TMockedUpdater::TSettings{ + .SelfTvmId = NOT_OK_CLIENT, + })); + UNIT_ASSERT_VALUES_EQUAL(ETicketStatus::InvalidDst, f.CheckServiceTicket(SRV_TICKET_123).GetStatus()); + UNIT_ASSERT_VALUES_EQUAL(ETicketStatus::InvalidDst, f.CheckServiceTicket(SRV_TICKET_456).GetStatus()); + UNIT_ASSERT_VALUES_EQUAL(ETicketStatus::Malformed, f.CheckServiceTicket("asdfg").GetStatus()); + } + { + TServiceContext::TCheckFlags checkFlags; + checkFlags.NeedDstCheck = false; + TTvmClient f( + new TMockedUpdater( + TMockedUpdater::TSettings{ + .SelfTvmId = NOT_OK_CLIENT}), + checkFlags); + UNIT_ASSERT_VALUES_EQUAL(ETicketStatus::Ok, f.CheckServiceTicket(SRV_TICKET_123).GetStatus()); + UNIT_ASSERT_VALUES_EQUAL(ETicketStatus::Ok, f.CheckServiceTicket(SRV_TICKET_456).GetStatus()); + UNIT_ASSERT_VALUES_EQUAL(ETicketStatus::Malformed, f.CheckServiceTicket("asdfg").GetStatus()); + UNIT_ASSERT_VALUES_EQUAL(f.CheckServiceTicket(SRV_TICKET_123).GetDst(), OK_CLIENT); + } + } +} diff --git a/library/cpp/tvmauth/client/ut/files/ok.cache b/library/cpp/tvmauth/client/ut/files/ok.cache Binary files differnew file mode 100644 index 0000000000..768d4953d1 --- /dev/null +++ b/library/cpp/tvmauth/client/ut/files/ok.cache diff --git a/library/cpp/tvmauth/client/ut/files/public_keys b/library/cpp/tvmauth/client/ut/files/public_keys Binary files differnew file mode 100644 index 0000000000..fa683d18f3 --- /dev/null +++ b/library/cpp/tvmauth/client/ut/files/public_keys diff --git a/library/cpp/tvmauth/client/ut/files/roles b/library/cpp/tvmauth/client/ut/files/roles Binary files differnew file mode 100644 index 0000000000..36864ae50a --- /dev/null +++ b/library/cpp/tvmauth/client/ut/files/roles diff --git a/library/cpp/tvmauth/client/ut/files/service_tickets b/library/cpp/tvmauth/client/ut/files/service_tickets Binary files differnew file mode 100644 index 0000000000..7a6985a34d --- /dev/null +++ b/library/cpp/tvmauth/client/ut/files/service_tickets diff --git a/library/cpp/tvmauth/client/ut/last_error_ut.cpp b/library/cpp/tvmauth/client/ut/last_error_ut.cpp new file mode 100644 index 0000000000..6751e78be7 --- /dev/null +++ b/library/cpp/tvmauth/client/ut/last_error_ut.cpp @@ -0,0 +1,56 @@ +#include <library/cpp/tvmauth/client/misc/last_error.h> + +#include <library/cpp/testing/unittest/registar.h> + +using namespace NTvmAuth; + +Y_UNIT_TEST_SUITE(LastError) { + Y_UNIT_TEST(common) { + TLastError le; + + UNIT_ASSERT_VALUES_EQUAL("OK", + le.GetLastError(true)); + UNIT_ASSERT_VALUES_EQUAL("Internal client error: failed to collect last useful error message, please report this message to tvm-dev@yandex-team.ru", + le.GetLastError(false)); + + UNIT_ASSERT_EXCEPTION_CONTAINS(le.ThrowLastError(), + TNonRetriableException, + "Internal client error: failed to collect last useful error message"); + + le.ProcessError(TLastError::EType::Retriable, TLastError::EScope::PublicKeys, "err_re#1"); + UNIT_ASSERT_VALUES_EQUAL("PublicKeys: err_re#1", + le.GetLastError(false)); + le.ProcessError(TLastError::EType::Retriable, TLastError::EScope::PublicKeys, "err_re#2"); + UNIT_ASSERT_VALUES_EQUAL("PublicKeys: err_re#2", + le.GetLastError(false)); + le.ProcessError(TLastError::EType::NonRetriable, TLastError::EScope::PublicKeys, "err_nonre#3"); + UNIT_ASSERT_VALUES_EQUAL("PublicKeys: err_nonre#3", + le.GetLastError(false)); + le.ProcessError(TLastError::EType::NonRetriable, TLastError::EScope::PublicKeys, "err_nonre#4"); + UNIT_ASSERT_VALUES_EQUAL("PublicKeys: err_nonre#4", + le.GetLastError(false)); + le.ProcessError(TLastError::EType::Retriable, TLastError::EScope::PublicKeys, "err_re#5"); + UNIT_ASSERT_VALUES_EQUAL("PublicKeys: err_nonre#4", + le.GetLastError(false)); + UNIT_ASSERT_EXCEPTION_CONTAINS(le.ThrowLastError(), + TNonRetriableException, + "Failed to start TvmClient. Do not retry: PublicKeys: err_nonre#4"); + + le.ProcessError(TLastError::EType::Retriable, TLastError::EScope::ServiceTickets, "err_re#6"); + UNIT_ASSERT_VALUES_EQUAL("PublicKeys: err_nonre#4", + le.GetLastError(false)); + le.ProcessError(TLastError::EType::Retriable, TLastError::EScope::ServiceTickets, "err_re#7"); + UNIT_ASSERT_VALUES_EQUAL("PublicKeys: err_nonre#4", + le.GetLastError(false)); + le.ProcessError(TLastError::EType::NonRetriable, TLastError::EScope::ServiceTickets, "err_nonre#8"); + UNIT_ASSERT_VALUES_EQUAL("ServiceTickets: err_nonre#8", + le.GetLastError(false)); + + le.ClearError(TLastError::EScope::ServiceTickets); + UNIT_ASSERT_VALUES_EQUAL("PublicKeys: err_nonre#4", + le.GetLastError(false)); + le.ClearError(TLastError::EScope::PublicKeys); + UNIT_ASSERT_VALUES_EQUAL("Internal client error: failed to collect last useful error message, please report this message to tvm-dev@yandex-team.ru", + le.GetLastError(false)); + } +} diff --git a/library/cpp/tvmauth/client/ut/logger_ut.cpp b/library/cpp/tvmauth/client/ut/logger_ut.cpp new file mode 100644 index 0000000000..76236e8913 --- /dev/null +++ b/library/cpp/tvmauth/client/ut/logger_ut.cpp @@ -0,0 +1,43 @@ +#include "common.h" + +#include <library/cpp/tvmauth/client/logger.h> + +#include <library/cpp/testing/unittest/registar.h> + +using namespace NTvmAuth; + +Y_UNIT_TEST_SUITE(ClientLogger) { + int i = 0; + + Y_UNIT_TEST(Debug) { + TLogger l; + l.Debug("qwerty"); + UNIT_ASSERT_VALUES_EQUAL("7: qwerty\n", l.Stream.Str()); + } + + Y_UNIT_TEST(Info) { + TLogger l; + l.Info("qwerty"); + UNIT_ASSERT_VALUES_EQUAL("6: qwerty\n", l.Stream.Str()); + } + + Y_UNIT_TEST(Warning) { + TLogger l; + l.Warning("qwerty"); + UNIT_ASSERT_VALUES_EQUAL("4: qwerty\n", l.Stream.Str()); + } + + Y_UNIT_TEST(Error) { + TLogger l; + l.Error("qwerty"); + UNIT_ASSERT_VALUES_EQUAL("3: qwerty\n", l.Stream.Str()); + } + +#ifdef _unix_ + Y_UNIT_TEST(Cerr_) { + TCerrLogger l(5); + l.Error("hit"); + l.Debug("miss"); + } +#endif +} diff --git a/library/cpp/tvmauth/client/ut/roles/decoder_ut.cpp b/library/cpp/tvmauth/client/ut/roles/decoder_ut.cpp new file mode 100644 index 0000000000..0ee5fc7cb7 --- /dev/null +++ b/library/cpp/tvmauth/client/ut/roles/decoder_ut.cpp @@ -0,0 +1,163 @@ +#include <library/cpp/tvmauth/client/exception.h> +#include <library/cpp/tvmauth/client/misc/roles/decoder.h> + +#include <library/cpp/tvmauth/unittest.h> +#include <library/cpp/tvmauth/src/utils.h> + +#include <library/cpp/testing/unittest/registar.h> + +using namespace NTvmAuth; +using namespace NTvmAuth::NRoles; + +Y_UNIT_TEST_SUITE(Decoder) { + const TString BROTLI = NUtils::Base64url2bin("GyMAAAR0Y6ku58ObclAQzDweUSUwbdqc5yOOKgI"); + const TString GZIP = NUtils::Base64url2bin("H4sIAAAAAAAA_yrOz01VKEstqkTGCpm5BflFJYl5JQpJOflJgAAAAP__MbeeiSQAAAA"); + const TString ZSTD = NUtils::Base64url2bin("KLUv_QBY9AAAwHNvbWUgdmVyeSBpbXBvcnRhbnQgYmxvYgEAc-4IAQAA"); + + Y_UNIT_TEST(Decode) { + // Errs + UNIT_ASSERT_EXCEPTION_CONTAINS( + TDecoder::Decode( + "1:brotli:10000:88839244E8C7C426B20729AF1A13AD792C5FA83C7F2FB6ADCFC60DA1B5EF9603", + TString(BROTLI)), + yexception, + "Decoded blob has bad size: expected 10000, actual 36"); + UNIT_ASSERT_EXCEPTION_CONTAINS( + TDecoder::Decode( + "1:brotli:36:88839244E8C7C426B20729AF1A13AD792C5FA83C7F2FB6ADCFC60DA1B5EF0000", + TString(BROTLI)), + yexception, + "Decoded blob has bad sha256"); + + // OK + TString decoded; + UNIT_ASSERT_NO_EXCEPTION( + decoded = TDecoder::Decode("", "some veryveryveryvery important blob")); + UNIT_ASSERT_VALUES_EQUAL(decoded, "some veryveryveryvery important blob"); + + UNIT_ASSERT_NO_EXCEPTION( + decoded = TDecoder::Decode( + "1:brotli:36:88839244E8C7C426B20729AF1A13AD792C5FA83C7F2FB6ADCFC60DA1B5EF9603", + TString(BROTLI))); + UNIT_ASSERT_VALUES_EQUAL(decoded, "some veryveryveryvery important blob"); + + UNIT_ASSERT_NO_EXCEPTION( + decoded = TDecoder::Decode( + "1:gzip:36:88839244E8C7C426B20729AF1A13AD792C5FA83C7F2FB6ADCFC60DA1B5EF9603", + TString(GZIP))); + UNIT_ASSERT_VALUES_EQUAL(decoded, "some veryveryveryvery important blob"); + + UNIT_ASSERT_NO_EXCEPTION( + decoded = TDecoder::Decode( + "1:zstd:36:88839244E8C7C426B20729AF1A13AD792C5FA83C7F2FB6ADCFC60DA1B5EF9603", + TString(ZSTD))); + UNIT_ASSERT_VALUES_EQUAL(decoded, "some veryveryveryvery important blob"); + } + + Y_UNIT_TEST(UnknownCodecs) { + for (const TStringBuf codec : {"lz", "lzma", "kek"}) { + UNIT_ASSERT_EXCEPTION_CONTAINS( + TDecoder::DecodeImpl(codec, ""), + yexception, + TStringBuilder() << "unknown codec: '" << codec << "'"); + } + } + + Y_UNIT_TEST(ParseCodec) { + UNIT_ASSERT_EXCEPTION_CONTAINS( + TDecoder::ParseCodec("2:kek"), + yexception, + "unknown codec format version; known: 1; got: 2"); + + UNIT_ASSERT_EXCEPTION_CONTAINS( + TDecoder::ParseCodec("1:::"), + yexception, + "codec type is empty"); + + UNIT_ASSERT_EXCEPTION_CONTAINS( + TDecoder::ParseCodec("1:some_codec:asd:"), + yexception, + "decoded blob size is not number"); + + UNIT_ASSERT_EXCEPTION_CONTAINS( + TDecoder::ParseCodec("1:some_codec:789:qwe"), + yexception, + "sha256 of decoded blob has invalid length: expected 64, got 3"); + + TDecoder::TCodecInfo info; + UNIT_ASSERT_NO_EXCEPTION( + info = TDecoder::ParseCodec("1:some_codec:789:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")); + + UNIT_ASSERT_VALUES_EQUAL("some_codec", info.Type); + UNIT_ASSERT_VALUES_EQUAL(789, info.Size); + UNIT_ASSERT_VALUES_EQUAL("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + info.Sha256); + } + + Y_UNIT_TEST(DecodeBrolti) { + UNIT_ASSERT_EXCEPTION( + TDecoder::DecodeBrolti(""), + yexception); + + TString blob; + UNIT_ASSERT_NO_EXCEPTION( + blob = TDecoder::DecodeBrolti( + TString(BROTLI))); + + UNIT_ASSERT_VALUES_EQUAL( + "some veryveryveryvery important blob", + blob); + } + + Y_UNIT_TEST(DecodeGzip) { + TString blob; + UNIT_ASSERT_NO_EXCEPTION(blob = TDecoder::DecodeGzip("")); + UNIT_ASSERT_VALUES_EQUAL("", blob); + + UNIT_ASSERT_NO_EXCEPTION( + blob = TDecoder::DecodeGzip( + TString(GZIP))); + + UNIT_ASSERT_VALUES_EQUAL( + "some veryveryveryvery important blob", + blob); + } + + Y_UNIT_TEST(DecodeZstd) { + TString blob; + UNIT_ASSERT_NO_EXCEPTION(blob = TDecoder::DecodeZstd("")); + UNIT_ASSERT_VALUES_EQUAL("", blob); + + UNIT_ASSERT_NO_EXCEPTION( + blob = TDecoder::DecodeZstd( + TString(ZSTD))); + + UNIT_ASSERT_VALUES_EQUAL( + "some veryveryveryvery important blob", + blob); + } + + Y_UNIT_TEST(VerifySize) { + UNIT_ASSERT_EXCEPTION_CONTAINS( + TDecoder::VerifySize("qwerty", 100), + yexception, + TStringBuilder() << "Decoded blob has bad size: expected 100, actual 6"); + + UNIT_ASSERT_NO_EXCEPTION(TDecoder::VerifySize("qwert", 5)); + } + + Y_UNIT_TEST(VerifyChecksum) { + UNIT_ASSERT_EXCEPTION_CONTAINS( + TDecoder::VerifyChecksum("qwerty", "zzzz"), + yexception, + "Decoded blob has bad sha256: expected=zzzz," + " actual=65E84BE33532FB784C48129675F9EFF3A682B27168C0EA744B2CF58EE02337C5"); + + UNIT_ASSERT_NO_EXCEPTION( + TDecoder::VerifyChecksum("qwerty", + "65E84BE33532FB784C48129675F9EFF3A682B27168C0EA744B2CF58EE02337C5")); + UNIT_ASSERT_NO_EXCEPTION( + TDecoder::VerifyChecksum("qwerty", + "65e84be33532fb784c48129675f9eff3a682b27168c0ea744b2cf58ee02337c5")); + } +} diff --git a/library/cpp/tvmauth/client/ut/roles/entities_index_ut.cpp b/library/cpp/tvmauth/client/ut/roles/entities_index_ut.cpp new file mode 100644 index 0000000000..7e62a87b64 --- /dev/null +++ b/library/cpp/tvmauth/client/ut/roles/entities_index_ut.cpp @@ -0,0 +1,358 @@ +#include <library/cpp/tvmauth/client/ut/common.h> + +#include <library/cpp/tvmauth/client/misc/roles/entities_index.h> + +#include <library/cpp/testing/unittest/registar.h> + +#include <array> + +using namespace NTvmAuth::NRoles; + +Y_UNIT_TEST_SUITE(RolesEntitiesIndex) { + Y_UNIT_TEST(Stage) { + TEntitiesIndex::TStage stage({ + "key#1", + "key#2", + "key#3", + "key#4", + }); + + const std::vector<std::vector<TString>> results = { + {"key#1"}, + {"key#2"}, + {"key#1", "key#2"}, + {"key#3"}, + {"key#1", "key#3"}, + {"key#2", "key#3"}, + {"key#1", "key#2", "key#3"}, + {"key#4"}, + {"key#1", "key#4"}, + {"key#2", "key#4"}, + {"key#1", "key#2", "key#4"}, + {"key#3", "key#4"}, + {"key#1", "key#3", "key#4"}, + {"key#2", "key#3", "key#4"}, + {"key#1", "key#2", "key#3", "key#4"}, + }; + + std::vector<TString> keys; + for (const std::vector<TString>& res : results) { + UNIT_ASSERT(stage.GetNextKeySet(keys)); + UNIT_ASSERT_VALUES_EQUAL(keys, res); + } + + UNIT_ASSERT_C(!stage.GetNextKeySet(keys), keys); + } + + Y_UNIT_TEST(GetUniqueSortedKeys) { + std::vector<TEntityPtr> entities; + + UNIT_ASSERT_VALUES_EQUAL(std::set<TString>(), + TEntitiesIndex::GetUniqueSortedKeys(entities)); + + entities = { + std::make_shared<TEntity>(), + }; + UNIT_ASSERT_VALUES_EQUAL(std::set<TString>(), + TEntitiesIndex::GetUniqueSortedKeys(entities)); + + entities = { + std::make_shared<TEntity>(TEntity{ + {"key#1", "value#1"}, + }), + }; + UNIT_ASSERT_VALUES_EQUAL(std::set<TString>({ + "key#1", + }), + TEntitiesIndex::GetUniqueSortedKeys(entities)); + + entities = { + std::make_shared<TEntity>(TEntity{ + {"key#1", "value#1"}, + }), + std::make_shared<TEntity>(TEntity{ + {"key#1", "value#11"}, + {"key#2", "value#22"}, + }), + }; + UNIT_ASSERT_VALUES_EQUAL(std::set<TString>({ + "key#1", + "key#2", + }), + TEntitiesIndex::GetUniqueSortedKeys(entities)); + } + + Y_UNIT_TEST(MakeUnique) { + const TEntityPtr entityA = std::make_shared<TEntity>(TEntity{{"key#1", "aaaa"}}); + const TEntityPtr entityA2 = std::make_shared<TEntity>(TEntity{{"key#1", "aaaa"}}); + const TEntityPtr entityB = std::make_shared<TEntity>(TEntity{{"key#1", "bbbb"}}); + + TEntitiesIndex::TSubTree idx = { + std::vector<TEntityPtr>{ + entityA, + entityA, + }, + TEntitiesIndex::TIdxByAttrs{ + { + TKeyValue{"key#1", "value#11"}, + TEntitiesIndex::TSubTree{ + std::vector<TEntityPtr>{ + entityA, + entityB, + entityA, + }, + TEntitiesIndex::TIdxByAttrs{ + { + TKeyValue{"key#2", "value#21"}, + TEntitiesIndex::TSubTree{ + std::vector<TEntityPtr>{ + entityA, + entityB, + entityA, + }, + TEntitiesIndex::TIdxByAttrs{}, + }, + }, + }, + }, + }, + { + TKeyValue{"key#1", "value#12"}, + TEntitiesIndex::TSubTree{ + std::vector<TEntityPtr>{ + entityA, + entityB, + entityA2, + }, + TEntitiesIndex::TIdxByAttrs{}, + }, + }, + }, + }; + + TEntitiesIndex::MakeUnique(idx); + + UNIT_ASSERT_VALUES_EQUAL(idx.Entities.size(), 1); + + auto it = idx.SubTree.find(TKeyValue{"key#1", "value#12"}); + UNIT_ASSERT(it != idx.SubTree.end()); + UNIT_ASSERT_VALUES_EQUAL(it->second.Entities.size(), 2); + + it = idx.SubTree.find(TKeyValue{"key#1", "value#11"}); + UNIT_ASSERT(it != idx.SubTree.end()); + UNIT_ASSERT_VALUES_EQUAL(it->second.Entities.size(), 2); + + it = it->second.SubTree.find(TKeyValue{"key#2", "value#21"}); + UNIT_ASSERT(it != it->second.SubTree.end()); + UNIT_ASSERT_VALUES_EQUAL(it->second.Entities.size(), 2); + } + + Y_UNIT_TEST(GetByAttrs) { + const TEntitiesIndex index = CreateEntitiesIndex(); + + UNIT_ASSERT_STRINGS_EQUAL( + index.PrintDebugString(), + R"( +"key#1/value#11" + "key#2/value#22" + "key#3/value#33" + "key#2/value#23" + "key#3/value#33" + "key#3/value#33" +"key#1/value#13" + "key#3/value#33" +"key#2/value#22" + "key#3/value#33" +"key#2/value#23" + "key#3/value#33" +"key#3/value#33" +)"); + + struct TCase { + TEntity AttrsToFind; + std::vector<TEntity> Result; + }; + + std::vector<TCase> cases = { + { + TEntity{}, + std::vector<TEntity>{ + TEntity{ + {"key#1", "value#11"}, + }, + TEntity{ + {"key#1", "value#11"}, + {"key#2", "value#22"}, + {"key#3", "value#33"}, + }, + TEntity{ + {"key#1", "value#11"}, + {"key#2", "value#23"}, + {"key#3", "value#33"}, + }, + TEntity{ + {"key#1", "value#13"}, + {"key#3", "value#33"}, + }, + }, + }, + { + TEntity{ + {"key#1", "value#11"}, + }, + std::vector<TEntity>{ + TEntity{ + {"key#1", "value#11"}, + }, + TEntity{ + {"key#1", "value#11"}, + {"key#2", "value#22"}, + {"key#3", "value#33"}, + }, + TEntity{ + {"key#1", "value#11"}, + {"key#2", "value#23"}, + {"key#3", "value#33"}, + }, + }, + }, + { + TEntity{ + {"key#1", "value#13"}, + }, + std::vector<TEntity>{ + TEntity{ + {"key#1", "value#13"}, + {"key#3", "value#33"}, + }, + }, + }, + { + TEntity{ + {"key#1", "value#14"}, + }, + std::vector<TEntity>{}, + }, + { + TEntity{ + {"key#2", "value#22"}, + }, + std::vector<TEntity>{ + TEntity{ + {"key#1", "value#11"}, + {"key#2", "value#22"}, + {"key#3", "value#33"}, + }, + }, + }, + { + TEntity{ + {"key#3", "value#33"}, + }, + std::vector<TEntity>{ + TEntity{ + {"key#1", "value#11"}, + {"key#2", "value#22"}, + {"key#3", "value#33"}, + }, + TEntity{ + {"key#1", "value#11"}, + {"key#2", "value#23"}, + {"key#3", "value#33"}, + }, + TEntity{ + {"key#1", "value#13"}, + {"key#3", "value#33"}, + }, + }, + }, + }; + + for (const TCase& c : cases) { + std::vector<TEntityPtr> expected; + for (const TEntity& e : c.Result) { + expected.push_back(std::make_shared<TEntity>(e)); + } + + UNIT_ASSERT_VALUES_EQUAL_C( + index.GetEntitiesWithAttrs(c.AttrsToFind.begin(), c.AttrsToFind.end()), + expected, + "'" << c.AttrsToFind << "'"); + } + } + + Y_UNIT_TEST(Contains) { + const TEntitiesIndex index = CreateEntitiesIndex(); + + struct TCase { + TEntity Exact; + bool Result = false; + }; + + std::vector<TCase> cases = { + { + TEntity{}, + false, + }, + { + TEntity{ + {"key#1", "value#11"}, + }, + true, + }, + { + TEntity{ + {"key#1", "value#13"}, + }, + false, + }, + { + TEntity{ + {"key#1", "value#13"}, + {"key#3", "value#33"}, + }, + true, + }, + }; + + for (const TCase& c : cases) { + UNIT_ASSERT_VALUES_EQUAL_C( + index.ContainsExactEntity(c.Exact.begin(), c.Exact.end()), + c.Result, + "'" << c.Exact << "'"); + } + } +} + +template <> +void Out<std::vector<TString>>(IOutputStream& o, const std::vector<TString>& s) { + for (const auto& key : s) { + o << key << ","; + } +} + +template <> +void Out<std::set<TString>>(IOutputStream& o, const std::set<TString>& s) { + for (const auto& key : s) { + o << key << ","; + } +} + +template <> +void Out<std::vector<TEntityPtr>>(IOutputStream& o, const std::vector<TEntityPtr>& v) { + for (const TEntityPtr& p : v) { + o << *p << Endl; + } +} + +template <> +void Out<TEntityPtr>(IOutputStream& o, const TEntityPtr& v) { + o << *v; +} + +template <> +void Out<TEntity>(IOutputStream& o, const TEntity& v) { + for (const auto& [key, value] : v) { + o << key << "->" << value << Endl; + } +} diff --git a/library/cpp/tvmauth/client/ut/roles/parser_ut.cpp b/library/cpp/tvmauth/client/ut/roles/parser_ut.cpp new file mode 100644 index 0000000000..4a2afac483 --- /dev/null +++ b/library/cpp/tvmauth/client/ut/roles/parser_ut.cpp @@ -0,0 +1,161 @@ +#include <library/cpp/tvmauth/client/misc/roles/parser.h> + +#include <library/cpp/tvmauth/unittest.h> + +#include <library/cpp/json/json_reader.h> +#include <library/cpp/testing/unittest/registar.h> + +using namespace NTvmAuth; +using namespace NTvmAuth::NRoles; + +Y_UNIT_TEST_SUITE(Parser) { + static NJson::TJsonValue ToJsonValue(TStringBuf body) { + NJson::TJsonValue doc; + UNIT_ASSERT(NJson::ReadJsonTree(body, &doc)); + return doc; + } + + Y_UNIT_TEST(GetEntity) { + UNIT_ASSERT_EXCEPTION_CONTAINS( + TParser::GetEntity(ToJsonValue(R"({"scope": false})"), + "cons", + "read"), + yexception, + "entity is map (str->str), got value Boolean. consumer 'cons' with role 'read'"); + + TEntityPtr en; + UNIT_ASSERT_NO_EXCEPTION( + en = TParser::GetEntity(ToJsonValue(R"({})"), + "cons", + "read")); + UNIT_ASSERT_VALUES_EQUAL(en->size(), 0); + + UNIT_ASSERT_NO_EXCEPTION( + en = TParser::GetEntity(ToJsonValue(R"({"key1": "val1", "key2": "val2"})"), + "cons", + "read")); + UNIT_ASSERT_VALUES_EQUAL(en->size(), 2); + } + + Y_UNIT_TEST(GetEntities) { + UNIT_ASSERT_EXCEPTION_CONTAINS( + TParser::GetEntities(ToJsonValue(R"([{},[]])"), + "cons", + "read"), + yexception, + "role entity for role must be map: consumer 'cons' with role 'read' has Array"); + + TEntitiesPtr en; + UNIT_ASSERT_NO_EXCEPTION( + en = TParser::GetEntities(ToJsonValue(R"([])"), + "cons", + "read")); + UNIT_ASSERT(!en->Contains({})); + + UNIT_ASSERT_NO_EXCEPTION( + en = TParser::GetEntities(ToJsonValue(R"([{}])"), + "cons", + "read")); + UNIT_ASSERT(en->Contains({})); + } + + Y_UNIT_TEST(GetConsumer) { + UNIT_ASSERT_EXCEPTION_CONTAINS( + TParser::GetConsumer(ToJsonValue(R"({"role1": [],"role2": {}})"), + "cons"), + yexception, + "entities for roles must be array: 'role2' is Map"); + + TConsumerRolesPtr c; + UNIT_ASSERT_NO_EXCEPTION( + c = TParser::GetConsumer(ToJsonValue(R"({"role1": [],"role2": []})"), + "cons")); + UNIT_ASSERT_EQUAL(c->GetRoles().size(), 2); + UNIT_ASSERT(c->HasRole("role1")); + UNIT_ASSERT(c->HasRole("role2")); + UNIT_ASSERT(!c->HasRole("role3")); + } + + Y_UNIT_TEST(GetConsumers) { + TRoles::TTvmConsumers cons; + UNIT_ASSERT_NO_EXCEPTION( + cons = TParser::GetConsumers<TTvmId>(ToJsonValue(R"({})"), + "tvm")); + UNIT_ASSERT_VALUES_EQUAL(0, cons.size()); + + UNIT_ASSERT_NO_EXCEPTION( + cons = TParser::GetConsumers<TTvmId>(ToJsonValue(R"({"tvm": {}})"), + "tvm")); + UNIT_ASSERT_VALUES_EQUAL(0, cons.size()); + + UNIT_ASSERT_EXCEPTION_CONTAINS( + TParser::GetConsumers<TTvmId>(ToJsonValue(R"({"tvm": []})"), + "tvm"), + yexception, + "'tvm' must be object"); + + UNIT_ASSERT_EXCEPTION_CONTAINS( + TParser::GetConsumers<TTvmId>(ToJsonValue(R"({"tvm": {"asd": []}})"), + "tvm"), + yexception, + "roles for consumer must be map: 'asd' is Array"); + + UNIT_ASSERT_EXCEPTION_CONTAINS( + TParser::GetConsumers<TTvmId>(ToJsonValue(R"({"tvm": {"asd": {}}})"), + "tvm"), + yexception, + "id must be valid positive number of proper size for tvm. got 'asd'"); + UNIT_ASSERT_EXCEPTION_CONTAINS( + TParser::GetConsumers<TTvmId>(ToJsonValue(R"({"tvm": {"1120000000001062": {}}})"), + "tvm"), + yexception, + "id must be valid positive number of proper size for tvm. got '1120000000001062'"); + UNIT_ASSERT_NO_EXCEPTION( + TParser::GetConsumers<TUid>(ToJsonValue(R"({"user": {"1120000000001062": {}}})"), + "user")); + + UNIT_ASSERT_EXCEPTION_CONTAINS( + TParser::GetConsumers<TTvmId>(ToJsonValue(R"({"tvm": {"42": {}, "042": {}}})"), + "tvm"), + yexception, + "consumer duplicate detected: '42' for tvm"); + + UNIT_ASSERT_NO_EXCEPTION( + cons = TParser::GetConsumers<TTvmId>(ToJsonValue(R"({"tvm": {"42": {}}})"), + "tvm")); + UNIT_ASSERT_VALUES_EQUAL(1, cons.size()); + } + + Y_UNIT_TEST(GetMeta) { + UNIT_ASSERT_EXCEPTION_CONTAINS( + TParser::GetMeta(ToJsonValue(R"({})")), + yexception, + "Missing 'revision'"); + + UNIT_ASSERT_EXCEPTION_CONTAINS( + TParser::GetMeta(ToJsonValue(R"({"revision": null})")), + yexception, + "'revision' has unexpected type: Null"); + + UNIT_ASSERT_EXCEPTION_CONTAINS( + TParser::GetMeta(ToJsonValue(R"({"revision": 100500})")), + yexception, + "Missing 'born_date'"); + + UNIT_ASSERT_EXCEPTION_CONTAINS( + TParser::GetMeta(ToJsonValue(R"({"revision": 100500, "born_date": false})")), + yexception, + "key 'born_date' must be uint"); + + TRoles::TMeta meta; + UNIT_ASSERT_NO_EXCEPTION( + meta = TParser::GetMeta(ToJsonValue(R"({"revision": 100500, "born_date": 42})"))); + UNIT_ASSERT_VALUES_EQUAL("100500", meta.Revision); + UNIT_ASSERT_VALUES_EQUAL(TInstant::Seconds(42), meta.BornTime); + + UNIT_ASSERT_NO_EXCEPTION( + meta = TParser::GetMeta(ToJsonValue(R"({"revision": "100501", "born_date": 42})"))); + UNIT_ASSERT_VALUES_EQUAL("100501", meta.Revision); + UNIT_ASSERT_VALUES_EQUAL(TInstant::Seconds(42), meta.BornTime); + } +} diff --git a/library/cpp/tvmauth/client/ut/roles/roles_ut.cpp b/library/cpp/tvmauth/client/ut/roles/roles_ut.cpp new file mode 100644 index 0000000000..d485dd857a --- /dev/null +++ b/library/cpp/tvmauth/client/ut/roles/roles_ut.cpp @@ -0,0 +1,419 @@ +#include <library/cpp/tvmauth/client/ut/common.h> + +#include <library/cpp/tvmauth/client/exception.h> +#include <library/cpp/tvmauth/client/misc/roles/roles.h> + +#include <library/cpp/tvmauth/unittest.h> + +#include <library/cpp/testing/unittest/registar.h> + +#include <array> + +using namespace NTvmAuth; +using namespace NTvmAuth::NRoles; + +Y_UNIT_TEST_SUITE(Roles) { + Y_UNIT_TEST(EntContains) { + TEntities ent(CreateEntitiesIndex()); + + UNIT_ASSERT(ent.Contains({{"key#1", "value#11"}})); + UNIT_ASSERT(ent.Contains({ + {"key#1", "value#13"}, + {"key#3", "value#33"}, + })); + UNIT_ASSERT(!ent.Contains({{"key#111", "value#11"}})); + UNIT_ASSERT(!ent.Contains({ + {"key#111", "value#13"}, + {"key#3", "value#33"}, + })); + + // valid calls + { + std::array<const std::pair<TStringBuf, TString>, 1> arr = {{{"key#1", "value#11"}}}; + UNIT_ASSERT(ent.ContainsSortedUnique<TStringBuf>({arr.begin(), arr.end()})); + } + { + std::array<const std::pair<TString, TStringBuf>, 2> arr = {{ + {"key#1", "value#13"}, + {"key#3", "value#33"}, + }}; + bool res = ent.ContainsSortedUnique<TString, TStringBuf>({arr.begin(), arr.end()}); + UNIT_ASSERT(res); + } + { + std::array<const std::pair<TStringBuf, TStringBuf>, 1> arr = {{{"key#111", "value#11"}}}; + bool res = ent.ContainsSortedUnique<TStringBuf, TStringBuf>({arr.begin(), arr.end()}); + UNIT_ASSERT(!res); + } + { + std::array<const std::pair<TString, TString>, 2> arr = {{ + {"key#111", "value#13"}, + {"key#3", "value#33"}, + }}; + UNIT_ASSERT(!ent.ContainsSortedUnique({arr.begin(), arr.end()})); + } + + // invalid calls + { + std::array<const std::pair<TString, TString>, 2> arr = {{ + {"key#3", "value#33"}, + {"key#1", "value#13"}, + }}; + UNIT_ASSERT_EXCEPTION_CONTAINS( + ent.ContainsSortedUnique({arr.begin(), arr.end()}), + TIllegalUsage, + "attrs are not sorted: 'key#3' before 'key#1'"); + } + { + std::array<const std::pair<TString, TString>, 2> arr = {{ + {"key#1", "value#13"}, + {"key#1", "value#13"}, + }}; + UNIT_ASSERT_EXCEPTION_CONTAINS( + ent.ContainsSortedUnique({arr.begin(), arr.end()}), + TIllegalUsage, + "attrs are not unique: 'key#1'"); + } + } + + Y_UNIT_TEST(EntWithAttrs) { + TEntities ent(CreateEntitiesIndex()); + + UNIT_ASSERT_VALUES_EQUAL( + ent.GetEntitiesWithAttrs({{"key#1", "value#11"}}), + std::vector<TEntityPtr>({ + std::make_shared<TEntity>(TEntity{ + {"key#1", "value#11"}, + }), + std::make_shared<TEntity>(TEntity{ + {"key#1", "value#11"}, + {"key#2", "value#22"}, + {"key#3", "value#33"}, + }), + std::make_shared<TEntity>(TEntity{ + {"key#1", "value#11"}, + {"key#2", "value#23"}, + {"key#3", "value#33"}, + }), + })); + UNIT_ASSERT_VALUES_EQUAL( + ent.GetEntitiesWithAttrs({{"key#111", "value#11"}}), + std::vector<TEntityPtr>()); + + // valid calls + { + std::array<const std::pair<TStringBuf, TString>, 2> arr = {{ + {"key#1", "value#11"}, + {"key#3", "value#33"}, + }}; + auto vec = ent.GetEntitiesWithSortedUniqueAttrs<TStringBuf>({arr.begin(), arr.end()}); + UNIT_ASSERT_VALUES_EQUAL( + vec, + std::vector<TEntityPtr>({ + std::make_shared<TEntity>(TEntity{ + {"key#1", "value#11"}, + {"key#2", "value#22"}, + {"key#3", "value#33"}, + }), + std::make_shared<TEntity>(TEntity{ + {"key#1", "value#11"}, + {"key#2", "value#23"}, + {"key#3", "value#33"}, + }), + })); + } + { + std::array<const std::pair<TString, TString>, 2> arr = {{ + {"key#111", "value#13"}, + {"key#3", "value#33"}, + }}; + UNIT_ASSERT_VALUES_EQUAL( + ent.GetEntitiesWithSortedUniqueAttrs({arr.begin(), arr.end()}), + std::vector<TEntityPtr>()); + } + + // invalid calls + { + std::array<const std::pair<TString, TString>, 2> arr = {{ + {"key#3", "value#33"}, + {"key#1", "value#13"}, + }}; + UNIT_ASSERT_EXCEPTION_CONTAINS( + ent.GetEntitiesWithSortedUniqueAttrs({arr.begin(), arr.end()}), + TIllegalUsage, + "attrs are not sorted: 'key#3' before 'key#1'"); + } + { + std::array<const std::pair<TString, TString>, 2> arr = {{ + {"key#1", "value#13"}, + {"key#1", "value#13"}, + }}; + UNIT_ASSERT_EXCEPTION_CONTAINS( + ent.GetEntitiesWithSortedUniqueAttrs({arr.begin(), arr.end()}), + TIllegalUsage, + "attrs are not unique: 'key#1'"); + } + } + + Y_UNIT_TEST(Consumer) { + TConsumerRoles c({ + {"read", std::make_shared<TEntities>(CreateEntitiesIndex())}, + {"write", std::make_shared<TEntities>(CreateEntitiesIndex())}, + }); + + UNIT_ASSERT_EQUAL(c.GetRoles().size(), 2); + UNIT_ASSERT(c.HasRole("read")); + UNIT_ASSERT(c.HasRole("write")); + UNIT_ASSERT(!c.HasRole("access")); + + UNIT_ASSERT_EQUAL(nullptr, c.GetEntitiesForRole("access")); + + TEntitiesPtr ent = c.GetEntitiesForRole("read"); + UNIT_ASSERT_UNEQUAL(nullptr, ent); + UNIT_ASSERT(ent->Contains({{"key#1", "value#11"}})); + UNIT_ASSERT(!ent->Contains({{"key#111", "value#11"}})); + + UNIT_ASSERT(c.CheckRoleForExactEntity("read", {{"key#1", "value#11"}})); + UNIT_ASSERT(!c.CheckRoleForExactEntity("access", {{"key#1", "value#11"}})); + UNIT_ASSERT(!c.CheckRoleForExactEntity("read", {{"key#111", "value#11"}})); + UNIT_ASSERT(!c.CheckRoleForExactEntity("read", {})); + } + + Y_UNIT_TEST(RolesService) { + TRoles r( + {}, + { + {100500, std::make_shared<TConsumerRoles>(TEntitiesByRoles{ + {"write", std::make_shared<TEntities>(CreateEntitiesIndex())}, + })}, + }, + {}, + std::make_shared<TString>()); + + UNIT_ASSERT_EXCEPTION_CONTAINS( + r.GetRolesForService(NUnittest::CreateServiceTicket( + ETicketStatus::InvalidDst, + 100500)), + TIllegalUsage, + "Service ticket must be valid, got: InvalidDst"); + + TConsumerRolesPtr cons; + UNIT_ASSERT_NO_EXCEPTION( + cons = r.GetRolesForService(NUnittest::CreateServiceTicket( + ETicketStatus::Ok, + 100501))); + UNIT_ASSERT_EQUAL(nullptr, cons); + + cons = r.GetRolesForService(NUnittest::CreateServiceTicket( + ETicketStatus::Ok, + 100500)); + UNIT_ASSERT_UNEQUAL(nullptr, cons); + UNIT_ASSERT_EQUAL(cons->GetRoles().size(), 1); + UNIT_ASSERT(!cons->HasRole("read")); + UNIT_ASSERT(cons->HasRole("write")); + + ////shortcuts + // no tvmid + UNIT_ASSERT(!r.CheckServiceRole( + NUnittest::CreateServiceTicket( + ETicketStatus::Ok, + 100501), + "write")); + + // no role + UNIT_ASSERT(!r.CheckServiceRole( + NUnittest::CreateServiceTicket( + ETicketStatus::Ok, + 100500), + "read")); + + // success + UNIT_ASSERT(r.CheckServiceRole( + NUnittest::CreateServiceTicket( + ETicketStatus::Ok, + 100500), + "write")); + + // no tvmid + UNIT_ASSERT(!r.CheckServiceRoleForExactEntity( + NUnittest::CreateServiceTicket( + ETicketStatus::Ok, + 100501), + "write", + {{"key#1", "value#11"}})); + + // no role + UNIT_ASSERT(!r.CheckServiceRoleForExactEntity( + NUnittest::CreateServiceTicket( + ETicketStatus::Ok, + 100500), + "read", + {{"key#1", "value#11"}})); + + // no entity + UNIT_ASSERT(!r.CheckServiceRoleForExactEntity( + NUnittest::CreateServiceTicket( + ETicketStatus::Ok, + 100500), + "write", + {{"key#111", "value#11"}})); + + // success + UNIT_ASSERT(r.CheckServiceRoleForExactEntity( + NUnittest::CreateServiceTicket( + ETicketStatus::Ok, + 100500), + "write", + {{"key#1", "value#11"}})); + } + + Y_UNIT_TEST(RolesUser) { + TRoles r( + {}, + {}, + { + {789654, std::make_shared<TConsumerRoles>(TEntitiesByRoles{ + {"read", std::make_shared<TEntities>(CreateEntitiesIndex())}, + })}, + }, + std::make_shared<TString>("some roles")); + + UNIT_ASSERT_VALUES_EQUAL("some roles", r.GetRaw()); + + UNIT_ASSERT_EXCEPTION_CONTAINS( + r.GetRolesForUser(NUnittest::CreateUserTicket( + ETicketStatus::Malformed, + 789654, + {})), + TIllegalUsage, + "User ticket must be valid, got: Malformed"); + + UNIT_ASSERT_EXCEPTION_CONTAINS( + r.GetRolesForUser(NUnittest::CreateUserTicket( + ETicketStatus::Ok, + 789654, + {}), + 789123), + TIllegalUsage, + "User ticket must be from ProdYateam, got from Test"); + + UNIT_ASSERT_EXCEPTION_CONTAINS( + r.GetRolesForUser(NUnittest::CreateUserTicket( + ETicketStatus::Ok, + 789654, + {}, + {}, + EBlackboxEnv::ProdYateam), + 789123), + TIllegalUsage, + "selectedUid must be in user ticket but it's not: 789123"); + + TConsumerRolesPtr cons; + UNIT_ASSERT_NO_EXCEPTION( + cons = r.GetRolesForUser(NUnittest::CreateUserTicket( + ETicketStatus::Ok, + 789123, + {}, + {}, + EBlackboxEnv::ProdYateam))); + UNIT_ASSERT_EQUAL(nullptr, cons); + + cons = r.GetRolesForUser(NUnittest::CreateUserTicket( + ETicketStatus::Ok, + 789654, + {}, + {}, + EBlackboxEnv::ProdYateam)); + UNIT_ASSERT_UNEQUAL(nullptr, cons); + UNIT_ASSERT_EQUAL(cons->GetRoles().size(), 1); + UNIT_ASSERT(cons->HasRole("read")); + UNIT_ASSERT(!cons->HasRole("write")); + + cons = r.GetRolesForUser(NUnittest::CreateUserTicket( + ETicketStatus::Ok, + 789123, + {}, + {789654, 789741}, + EBlackboxEnv::ProdYateam), + 789654); + UNIT_ASSERT_UNEQUAL(nullptr, cons); + UNIT_ASSERT_EQUAL(cons->GetRoles().size(), 1); + UNIT_ASSERT(cons->HasRole("read")); + UNIT_ASSERT(!cons->HasRole("write")); + + ////shortcuts + // no uid + UNIT_ASSERT(!r.CheckUserRole( + NUnittest::CreateUserTicket( + ETicketStatus::Ok, + 789123, + {}, + {}, + EBlackboxEnv::ProdYateam), + "read")); + + // no role + UNIT_ASSERT(!r.CheckUserRole( + NUnittest::CreateUserTicket( + ETicketStatus::Ok, + 789654, + {}, + {}, + EBlackboxEnv::ProdYateam), + "wrire")); + + // success + UNIT_ASSERT(r.CheckUserRole( + NUnittest::CreateUserTicket( + ETicketStatus::Ok, + 789654, + {}, + {}, + EBlackboxEnv::ProdYateam), + "read")); + + // no uid + UNIT_ASSERT(!r.CheckUserRoleForExactEntity( + NUnittest::CreateUserTicket( + ETicketStatus::Ok, + 789123, + {}, + {}, + EBlackboxEnv::ProdYateam), + "read", + {{"key#1", "value#11"}})); + + // no role + UNIT_ASSERT(!r.CheckUserRoleForExactEntity( + NUnittest::CreateUserTicket( + ETicketStatus::Ok, + 789654, + {}, + {}, + EBlackboxEnv::ProdYateam), + "wrire", + {{"key#1", "value#11"}})); + + // no entity + UNIT_ASSERT(!r.CheckUserRoleForExactEntity( + NUnittest::CreateUserTicket( + ETicketStatus::Ok, + 789654, + {}, + {}, + EBlackboxEnv::ProdYateam), + "read", + {{"key#111", "value#11"}})); + + // success + UNIT_ASSERT(r.CheckUserRoleForExactEntity( + NUnittest::CreateUserTicket( + ETicketStatus::Ok, + 789654, + {}, + {}, + EBlackboxEnv::ProdYateam), + "read", + {{"key#1", "value#11"}})); + } +} diff --git a/library/cpp/tvmauth/client/ut/roles/tvmapi_roles_fetcher_ut.cpp b/library/cpp/tvmauth/client/ut/roles/tvmapi_roles_fetcher_ut.cpp new file mode 100644 index 0000000000..7eaf611e82 --- /dev/null +++ b/library/cpp/tvmauth/client/ut/roles/tvmapi_roles_fetcher_ut.cpp @@ -0,0 +1,197 @@ +#include <library/cpp/tvmauth/client/ut/common.h> + +#include <library/cpp/tvmauth/client/misc/disk_cache.h> +#include <library/cpp/tvmauth/client/misc/api/roles_fetcher.h> + +#include <library/cpp/tvmauth/unittest.h> + +#include <library/cpp/testing/unittest/registar.h> + +#include <util/stream/file.h> +#include <util/system/fs.h> + +using namespace NTvmAuth; +using namespace NTvmAuth::NTvmApi; + +Y_UNIT_TEST_SUITE(TvmApiRolesFetcher) { + static const TString ROLES = R"({"revision": "100501", "born_date": 42})"; + + static const TString CACHE_DIR = "./tmp/"; + + static void CleanCache() { + NFs::RemoveRecursive(CACHE_DIR); + NFs::MakeDirectoryRecursive(CACHE_DIR); + } + + Y_UNIT_TEST(ReadFromDisk) { + CleanCache(); + auto logger = MakeIntrusive<TLogger>(); + + TRolesFetcherSettings s; + s.CacheDir = CACHE_DIR; + s.SelfTvmId = 111111; + s.IdmSystemSlug = "fem\tida"; + TRolesFetcher fetcher(s, logger); + + UNIT_ASSERT(!fetcher.AreRolesOk()); + + UNIT_ASSERT_VALUES_EQUAL(TInstant(), fetcher.ReadFromDisk()); + UNIT_ASSERT_VALUES_EQUAL( + TStringBuilder() + << "7: File './tmp/roles' does not exist\n", + logger->Stream.Str()); + logger->Stream.clear(); + + const TInstant now = TInstant::Seconds(TInstant::Now().Seconds()); + + TDiskWriter wr(CACHE_DIR + "roles"); + UNIT_ASSERT(wr.Write("kek", now)); + UNIT_ASSERT_NO_EXCEPTION(fetcher.ReadFromDisk()); + UNIT_ASSERT(!fetcher.AreRolesOk()); + + UNIT_ASSERT_VALUES_EQUAL( + TStringBuilder() + << "6: File './tmp/roles' was successfully read\n" + << "4: Roles in disk cache are for another slug (kek). Self=fem\tida\n", + logger->Stream.Str()); + logger->Stream.clear(); + + UNIT_ASSERT(wr.Write(TRolesFetcher::PrepareDiskFormat(ROLES, "femida_test"), now)); + UNIT_ASSERT_VALUES_EQUAL(TInstant(), fetcher.ReadFromDisk()); + UNIT_ASSERT(!fetcher.AreRolesOk()); + + UNIT_ASSERT_VALUES_EQUAL( + TStringBuilder() + << "6: File './tmp/roles' was successfully read\n" + "4: Roles in disk cache are for another slug (femida_test). Self=fem\tida\n", + logger->Stream.Str()); + logger->Stream.clear(); + + UNIT_ASSERT(wr.Write(TRolesFetcher::PrepareDiskFormat(ROLES, "fem\tida"), now)); + UNIT_ASSERT_VALUES_EQUAL(now, fetcher.ReadFromDisk()); + UNIT_ASSERT(fetcher.AreRolesOk()); + + UNIT_ASSERT_VALUES_EQUAL( + TStringBuilder() + << "6: File './tmp/roles' was successfully read\n" + "7: Succeed to read roles with revision 100501 from ./tmp/roles\n", + logger->Stream.Str()); + logger->Stream.clear(); + } + + Y_UNIT_TEST(IsTimeToUpdate) { + TRetrySettings settings; + settings.RolesUpdatePeriod = TDuration::Minutes(123); + + UNIT_ASSERT(!TRolesFetcher::IsTimeToUpdate(settings, TDuration::Seconds(5))); + UNIT_ASSERT(TRolesFetcher::IsTimeToUpdate(settings, TDuration::Hours(5))); + } + + Y_UNIT_TEST(ShouldWarn) { + TRetrySettings settings; + settings.RolesWarnPeriod = TDuration::Minutes(123); + + UNIT_ASSERT(!TRolesFetcher::ShouldWarn(settings, TDuration::Seconds(5))); + UNIT_ASSERT(TRolesFetcher::ShouldWarn(settings, TDuration::Hours(5))); + } + + Y_UNIT_TEST(Update) { + CleanCache(); + auto logger = MakeIntrusive<TLogger>(); + + TRolesFetcherSettings s; + s.CacheDir = CACHE_DIR; + s.SelfTvmId = 111111; + TRolesFetcher fetcher(s, logger); + + UNIT_ASSERT(!fetcher.AreRolesOk()); + + NUtils::TFetchResult fetchResult; + fetchResult.Code = 304; + + UNIT_ASSERT_EXCEPTION_CONTAINS( + fetcher.Update(NUtils::TFetchResult(fetchResult)), + yexception, + "tirole did not return any roles because current roles are actual, but there are no roles in memory"); + UNIT_ASSERT(!fetcher.AreRolesOk()); + UNIT_ASSERT(!NFs::Exists(CACHE_DIR + "roles")); + + fetchResult.Code = 206; + UNIT_ASSERT_EXCEPTION_CONTAINS( + fetcher.Update(NUtils::TFetchResult(fetchResult)), + yexception, + "Unexpected code from tirole: 206."); + UNIT_ASSERT(!fetcher.AreRolesOk()); + UNIT_ASSERT(!NFs::Exists(CACHE_DIR + "roles")); + + fetchResult.Code = 200; + fetchResult.Response = "kek"; + UNIT_ASSERT_EXCEPTION_CONTAINS( + fetcher.Update(NUtils::TFetchResult(fetchResult)), + yexception, + "Invalid json. 'kek'"); + UNIT_ASSERT(!fetcher.AreRolesOk()); + UNIT_ASSERT(!NFs::Exists(CACHE_DIR + "roles")); + + fetchResult.Response = ROLES; + UNIT_ASSERT_NO_EXCEPTION(fetcher.Update(NUtils::TFetchResult(fetchResult))); + UNIT_ASSERT(fetcher.AreRolesOk()); + UNIT_ASSERT(NFs::Exists(CACHE_DIR + "roles")); + { + TFileInput f(CACHE_DIR + "roles"); + TString body = f.ReadAll(); + UNIT_ASSERT_C(body.Contains(ROLES), "got body: '" << body << "'"); + } + + fetchResult.Code = 304; + fetchResult.Response.clear(); + UNIT_ASSERT_NO_EXCEPTION(fetcher.Update(NUtils::TFetchResult(fetchResult))); + UNIT_ASSERT(fetcher.AreRolesOk()); + UNIT_ASSERT(NFs::Exists(CACHE_DIR + "roles")); + + fetchResult.Code = 200; + fetchResult.Headers.AddHeader("X-Tirole-Compression", "kek"); + UNIT_ASSERT_EXCEPTION_CONTAINS( + fetcher.Update(NUtils::TFetchResult(fetchResult)), + yexception, + "unknown codec format version; known: 1; got: kek"); + } + + Y_UNIT_TEST(CreateTiroleRequest) { + CleanCache(); + auto logger = MakeIntrusive<TLogger>(); + + TRolesFetcherSettings s; + s.CacheDir = CACHE_DIR; + s.SelfTvmId = 111111; + s.IdmSystemSlug = "some sys"; + TRolesFetcher fetcher(s, logger); + + TRolesFetcher::TRequest req = fetcher.CreateTiroleRequest("some_ticket"); + UNIT_ASSERT_VALUES_EQUAL( + "/v1/get_actual_roles?system_slug=some+sys&_pid=&lib_version=client_", + TStringBuf(req.Url).Chop(5)); + UNIT_ASSERT_VALUES_EQUAL( + TKeepAliveHttpClient::THeaders({ + {"X-Ya-Service-Ticket", "some_ticket"}, + }), + req.Headers); + + TDiskWriter wr(CACHE_DIR + "roles"); + UNIT_ASSERT(wr.Write(TRolesFetcher::PrepareDiskFormat( + R"({"revision": "asd&qwe", "born_date": 42})", + "some sys"))); + UNIT_ASSERT_NO_EXCEPTION(fetcher.ReadFromDisk()); + + req = fetcher.CreateTiroleRequest("some_ticket"); + UNIT_ASSERT_VALUES_EQUAL( + "/v1/get_actual_roles?system_slug=some+sys&_pid=&lib_version=client_", + TStringBuf(req.Url).Chop(5)); + UNIT_ASSERT_VALUES_EQUAL( + TKeepAliveHttpClient::THeaders({ + {"If-None-Match", R"("asd&qwe")"}, + {"X-Ya-Service-Ticket", "some_ticket"}, + }), + req.Headers); + } +} diff --git a/library/cpp/tvmauth/client/ut/roles/tvmtool_roles_fetcher_ut.cpp b/library/cpp/tvmauth/client/ut/roles/tvmtool_roles_fetcher_ut.cpp new file mode 100644 index 0000000000..55db4950ce --- /dev/null +++ b/library/cpp/tvmauth/client/ut/roles/tvmtool_roles_fetcher_ut.cpp @@ -0,0 +1,103 @@ +#include <library/cpp/tvmauth/client/ut/common.h> + +#include <library/cpp/tvmauth/client/misc/tool/roles_fetcher.h> + +#include <library/cpp/tvmauth/unittest.h> + +#include <library/cpp/testing/unittest/registar.h> + +using namespace NTvmAuth; +using namespace NTvmAuth::NTvmTool; + +Y_UNIT_TEST_SUITE(TvmToolRolesFetcher) { + static const TString ROLES = R"({"revision": "100501", "born_date": 42})"; + + Y_UNIT_TEST(IsTimeToUpdate) { + TRolesFetcher rf( + TRolesFetcherSettings{.UpdatePeriod = TDuration::Minutes(1)}, + new TLogger); + + UNIT_ASSERT(!rf.IsTimeToUpdate(TDuration::Seconds(3))); + UNIT_ASSERT(!rf.IsTimeToUpdate(TDuration::Seconds(60))); + UNIT_ASSERT(rf.IsTimeToUpdate(TDuration::Seconds(61))); + UNIT_ASSERT(rf.IsTimeToUpdate(TDuration::Seconds(600))); + } + + Y_UNIT_TEST(ShouldWarn) { + TRolesFetcher rf( + TRolesFetcherSettings{.WarnPeriod = TDuration::Minutes(20)}, + new TLogger); + + UNIT_ASSERT(!rf.ShouldWarn(TDuration::Minutes(3))); + UNIT_ASSERT(!rf.ShouldWarn(TDuration::Minutes(20))); + UNIT_ASSERT(rf.ShouldWarn(TDuration::Minutes(21))); + UNIT_ASSERT(rf.ShouldWarn(TDuration::Minutes(600))); + } + + Y_UNIT_TEST(Common) { + auto logger = MakeIntrusive<TLogger>(); + TRolesFetcher rf( + TRolesFetcherSettings{.SelfAlias = "some_alias"}, + logger); + UNIT_ASSERT(!rf.AreRolesOk()); + UNIT_ASSERT(!rf.GetCurrentRoles()); + + UNIT_ASSERT_EXCEPTION_CONTAINS( + rf.Update(NUtils::TFetchResult{.Code = HTTP_NOT_MODIFIED}), + yexception, + "tvmtool did not return any roles because current roles are actual, but there are no roles in memory - this should never happen"); + UNIT_ASSERT_EXCEPTION_CONTAINS( + rf.Update(NUtils::TFetchResult{.Code = HTTP_BAD_REQUEST, .Response = "kek"}), + yexception, + "Unexpected code from tvmtool: 400. kek"); + UNIT_ASSERT_EXCEPTION_CONTAINS( + rf.Update(NUtils::TFetchResult{.Code = HTTP_OK, .Response = "kek"}), + yexception, + "Invalid json. 'kek'"); + + UNIT_ASSERT_NO_EXCEPTION(rf.Update(NUtils::TFetchResult{.Code = HTTP_OK, .Response = ROLES})); + UNIT_ASSERT(rf.AreRolesOk()); + UNIT_ASSERT(rf.GetCurrentRoles()); + UNIT_ASSERT_VALUES_EQUAL("100501", rf.GetCurrentRoles()->GetMeta().Revision); + + UNIT_ASSERT_NO_EXCEPTION(rf.Update(NUtils::TFetchResult{.Code = HTTP_NOT_MODIFIED})); + UNIT_ASSERT_VALUES_EQUAL("100501", rf.GetCurrentRoles()->GetMeta().Revision); + + UNIT_ASSERT_VALUES_EQUAL( + "7: Succeed to update roles with revision 100501\n", + logger->Stream.Str()); + } + + Y_UNIT_TEST(CreateRequest) { + struct TTestFetcher: TRolesFetcher { + using TRolesFetcher::CreateRequest; + using TRolesFetcher::TRequest; + using TRolesFetcher::TRolesFetcher; + }; + + TTestFetcher rf( + TRolesFetcherSettings{.SelfAlias = "some_&alias"}, + new TLogger); + + TTestFetcher::TRequest request = rf.CreateRequest({{"some_header", "some_value"}}); + UNIT_ASSERT_VALUES_EQUAL( + "/v2/roles?self=some_%26alias", + request.Url); + UNIT_ASSERT_VALUES_EQUAL( + TKeepAliveHttpClient::THeaders({{"some_header", "some_value"}}), + request.Headers); + + UNIT_ASSERT_NO_EXCEPTION(rf.Update(NUtils::TFetchResult{.Code = HTTP_OK, .Response = ROLES})); + + request = rf.CreateRequest({{"some_header", "some_value"}}); + UNIT_ASSERT_VALUES_EQUAL( + "/v2/roles?self=some_%26alias", + request.Url); + UNIT_ASSERT_VALUES_EQUAL( + TKeepAliveHttpClient::THeaders({ + {"some_header", "some_value"}, + {"If-None-Match", R"("100501")"}, + }), + request.Headers); + } +} diff --git a/library/cpp/tvmauth/client/ut/settings_ut.cpp b/library/cpp/tvmauth/client/ut/settings_ut.cpp new file mode 100644 index 0000000000..726beaf928 --- /dev/null +++ b/library/cpp/tvmauth/client/ut/settings_ut.cpp @@ -0,0 +1,158 @@ +#include "common.h" + +#include <library/cpp/tvmauth/client/misc/api/settings.h> + +#include <library/cpp/testing/unittest/registar.h> + +using namespace NTvmAuth; + +Y_UNIT_TEST_SUITE(ClientSettings) { +#if !defined(_win_) + Y_UNIT_TEST(CheckValid) { + struct TTestCase { + TString Name; + NTvmApi::TClientSettings Settings; + TString Err; + }; + std::vector<TTestCase> cases = { + TTestCase{ + .Name = "default", + .Settings = {}, + .Err = "Invalid settings: nothing to do", + }, + TTestCase{ + .Name = "only secret", + .Settings = { + .Secret = TStringBuf("foobar"), + }, + .Err = "Secret is present but destinations list is empty. It makes no sense", + }, + TTestCase{ + .Name = "only dsts", + .Settings = { + .FetchServiceTicketsForDsts = {42}, + }, + .Err = "SelfTvmId cannot be 0 if fetching of Service Tickets required", + }, + TTestCase{ + .Name = "dsts with selfTvmId", + .Settings = { + .SelfTvmId = 43, + .FetchServiceTicketsForDsts = {42}, + }, + .Err = "Secret is required for fetching of Service Tickets", + }, + TTestCase{ + .Name = "correct service tickets fetching", + .Settings = { + .SelfTvmId = 43, + .Secret = TStringBuf("foobar"), + .FetchServiceTicketsForDsts = {42}, + }, + .Err = "", + }, + TTestCase{ + .Name = "only check srv flag", + .Settings = { + .CheckServiceTickets = true, + }, + .Err = "SelfTvmId cannot be 0 if checking of Service Tickets required", + }, + TTestCase{ + .Name = "tirole without disk cache", + .Settings = { + .SelfTvmId = 43, + .Secret = TStringBuf("foobar"), + .FetchRolesForIdmSystemSlug = "kek", + }, + .Err = "Disk cache must be enabled to use roles: they can be heavy", + }, + }; + + for (const TTestCase& c : cases) { + if (c.Err) { + UNIT_ASSERT_EXCEPTION_CONTAINS_C( + c.Settings.CheckValid(), + TBrokenTvmClientSettings, + c.Err, + c.Name); + } else { + UNIT_ASSERT_NO_EXCEPTION_C(c.Settings.CheckValid(), c.Name); + } + } + + NTvmApi::TClientSettings s{.DiskCacheDir = "/impossible/dir"}; + UNIT_ASSERT_EXCEPTION(s.CheckValid(), TPermissionDenied); + } + + Y_UNIT_TEST(CloneNormalized) { + NTvmApi::TClientSettings original; + original.FetchServiceTicketsForDsts = {43}; + + UNIT_ASSERT_EXCEPTION_CONTAINS(original.CloneNormalized(), + TBrokenTvmClientSettings, + "SelfTvmId cannot be 0 if fetching of Service Tickets required"); + original.SelfTvmId = 15; + original.Secret = "bar"; + original.DiskCacheDir = "./"; + + NTvmApi::TClientSettings::TDstVector expected = {43}; + UNIT_ASSERT_VALUES_EQUAL(expected, original.CloneNormalized().FetchServiceTicketsForDsts); + + original.FetchServiceTicketsForDstsWithAliases = {{"foo", 42}}; + expected = {42, 43}; + UNIT_ASSERT_VALUES_EQUAL(expected, original.CloneNormalized().FetchServiceTicketsForDsts); + + original.FetchRolesForIdmSystemSlug = "kek"; + expected = {42, 43, 2028120}; + UNIT_ASSERT_VALUES_EQUAL(expected, original.CloneNormalized().FetchServiceTicketsForDsts); + + original.FetchServiceTicketsForDsts.push_back(2028120); + expected = {42, 43, 2028120}; + UNIT_ASSERT_VALUES_EQUAL(expected, original.CloneNormalized().FetchServiceTicketsForDsts); + } + + Y_UNIT_TEST(NeedServiceTicketsFetching) { + NTvmApi::TClientSettings s; + + UNIT_ASSERT(!s.NeedServiceTicketsFetching()); + + s.FetchServiceTicketsForDsts = {42}; + UNIT_ASSERT(s.NeedServiceTicketsFetching()); + s.FetchServiceTicketsForDsts.clear(); + + s.FetchServiceTicketsForDstsWithAliases = {{"foo", 42}}; + UNIT_ASSERT(s.NeedServiceTicketsFetching()); + s.FetchServiceTicketsForDstsWithAliases.clear(); + + s.FetchRolesForIdmSystemSlug = "bar"; + UNIT_ASSERT(s.NeedServiceTicketsFetching()); + s.FetchRolesForIdmSystemSlug.clear(); + } + + Y_UNIT_TEST(permitions) { + UNIT_ASSERT_EXCEPTION(NTvmApi::TClientSettings::CheckPermissions("/qwerty"), TPermissionDenied); + + const TString tmpDir = "./cache_dir"; + + NFs::RemoveRecursive(tmpDir); + NFs::MakeDirectory(tmpDir, NFs::FP_OWNER_WRITE | NFs::FP_GROUP_WRITE | NFs::FP_ALL_WRITE); + UNIT_ASSERT_EXCEPTION(NTvmApi::TClientSettings::CheckPermissions(tmpDir), TPermissionDenied); + + NFs::RemoveRecursive(tmpDir); + NFs::MakeDirectory(tmpDir, NFs::FP_OWNER_READ | NFs::FP_GROUP_READ | NFs::FP_ALL_READ); + UNIT_ASSERT_EXCEPTION(NTvmApi::TClientSettings::CheckPermissions(tmpDir), TPermissionDenied); + + NFs::RemoveRecursive(tmpDir); + NFs::MakeDirectory(tmpDir, NFs::FP_COMMON_FILE); + UNIT_ASSERT_NO_EXCEPTION(NTvmApi::TClientSettings::CheckPermissions(tmpDir)); + } +#endif + + Y_UNIT_TEST(Dst) { + UNIT_ASSERT_EXCEPTION_CONTAINS(NTvmApi::TClientSettings::TDst(0), yexception, "TvmId cannot be 0"); + UNIT_ASSERT_EXCEPTION_CONTAINS(NTvmApi::TClientSettings::TDstMap({{"blackbox", 0}}), + TBrokenTvmClientSettings, + "TvmId cannot be 0"); + } +} diff --git a/library/cpp/tvmauth/client/ut/src_checker_ut.cpp b/library/cpp/tvmauth/client/ut/src_checker_ut.cpp new file mode 100644 index 0000000000..e3a4e3888a --- /dev/null +++ b/library/cpp/tvmauth/client/ut/src_checker_ut.cpp @@ -0,0 +1,41 @@ +#include "common.h" + +#include <library/cpp/tvmauth/client/mocked_updater.h> +#include <library/cpp/tvmauth/client/misc/src_checker.h> +#include <library/cpp/tvmauth/client/misc/api/threaded_updater.h> + +#include <library/cpp/tvmauth/type.h> +#include <library/cpp/tvmauth/unittest.h> + +#include <library/cpp/testing/unittest/registar.h> + +using namespace NTvmAuth; + +Y_UNIT_TEST_SUITE(SrcChecker) { + Y_UNIT_TEST(Check) { + NRoles::TRolesPtr roles = std::make_shared<NRoles::TRoles>( + NRoles::TRoles::TMeta{}, + NRoles::TRoles::TTvmConsumers{ + {12345, std::make_shared<NRoles::TConsumerRoles>( + THashMap<TString, NRoles::TEntitiesPtr>())}, + }, + NRoles::TRoles::TUserConsumers{}, + std::make_shared<TString>()); + + TAsyncUpdaterPtr u = new TMockedUpdater({.Roles = roles}); + auto r = u->GetRoles(); + UNIT_ASSERT_EXCEPTION_CONTAINS( + TSrcChecker::Check(NUnittest::CreateServiceTicket(ETicketStatus::Expired, 12345), r), + TIllegalUsage, + "Service ticket must be valid"); + + TCheckedServiceTicket ticket; + UNIT_ASSERT_NO_EXCEPTION( + ticket = TSrcChecker::Check(NUnittest::CreateServiceTicket(ETicketStatus::Ok, 12345), r)); + UNIT_ASSERT_VALUES_EQUAL(ETicketStatus::Ok, ticket.GetStatus()); + + UNIT_ASSERT_NO_EXCEPTION( + ticket = TSrcChecker::Check(NUnittest::CreateServiceTicket(ETicketStatus::Ok, 9999), r)); + UNIT_ASSERT_VALUES_EQUAL(ETicketStatus::NoRoles, ticket.GetStatus()); + } +} diff --git a/library/cpp/tvmauth/client/ut/tvmapi_updater_ut.cpp b/library/cpp/tvmauth/client/ut/tvmapi_updater_ut.cpp new file mode 100644 index 0000000000..ef1ebc8453 --- /dev/null +++ b/library/cpp/tvmauth/client/ut/tvmapi_updater_ut.cpp @@ -0,0 +1,1326 @@ +#include "common.h" + +#include <library/cpp/tvmauth/client/mocked_updater.h> +#include <library/cpp/tvmauth/client/misc/disk_cache.h> +#include <library/cpp/tvmauth/client/misc/api/threaded_updater.h> + +#include <library/cpp/testing/unittest/registar.h> +#include <library/cpp/testing/unittest/tests_data.h> + +#include <util/stream/file.h> +#include <util/string/subst.h> +#include <util/system/fs.h> + +#include <regex> + +using namespace NTvmAuth; +static const std::regex TIME_REGEX(R"(\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d.\d{6}Z)"); + +Y_UNIT_TEST_SUITE(ApiUpdater) { + static const TString SRV_TICKET = "3:serv:CBAQ__________9_IgYIexCUkQY:GioCM49Ob6_f80y6FY0XBVN4hLXuMlFeyMvIMiDuQnZkbkLpRpQOuQo5YjWoBjM0Vf-XqOm8B7xtrvxSYHDD7Q4OatN2l-Iwg7i71lE3scUeD36x47st3nd0OThvtjrFx_D8mw_c0GT5KcniZlqq1SjhLyAk1b_zJsx8viRAhCU"; + static const TString TEST_TICKET = "3:user:CA0Q__________9_Gg4KAgh7EHsg0oXYzAQoAQ:FSADps3wNGm92Vyb1E9IVq5M6ZygdGdt1vafWWEhfDDeCLoVA-sJesxMl2pGW4OxJ8J1r_MfpG3ZoBk8rLVMHUFrPa6HheTbeXFAWl8quEniauXvKQe4VyrpA1SPgtRoFqi5upSDIJzEAe1YRJjq1EClQ_slMt8R0kA_JjKUX54"; + static const TString TVM_RESPONSE = + R"({ + "19" : { "ticket" : "3:serv:CBAQ__________9_IgYIKhCUkQY:CX"}, + "213" : { "ticket" : "service_ticket_2"}, + "234" : { "error" : "Dst is not found" }, + "185" : { "ticket" : "service_ticket_3"}, + "deprecated" : { "ticket" : "deprecated_ticket" } + })"; + + static const TString CACHE_DIR = "./tmp/"; + + static void CleanCache() { + NFs::RemoveRecursive(CACHE_DIR); + NFs::MakeDirectoryRecursive(CACHE_DIR); + } + + Y_UNIT_TEST(MockedUpdater) { + TMockedUpdater m; + UNIT_ASSERT_VALUES_EQUAL(TClientStatus::Ok, m.GetStatus()); + UNIT_ASSERT(m.GetCachedServiceContext()->Check(SRV_TICKET)); + UNIT_ASSERT(m.GetCachedUserContext()->Check(TEST_TICKET)); + } + + Y_UNIT_TEST(Updater) { + NTvmApi::TClientSettings s{ + .DiskCacheDir = GetCachePath(), + .SelfTvmId = 100500, + .CheckServiceTickets = true, + }; + + auto l = MakeIntrusive<TLogger>(); + { + auto u = NTvmApi::TThreadedUpdater::Create(s, l); + UNIT_ASSERT_VALUES_EQUAL(TClientStatus::Ok, u->GetStatus()); + } + + UNIT_ASSERT_C(l->Stream.Str().find("was successfully read") != TString::npos, l->Stream.Str()); + UNIT_ASSERT_C(l->Stream.Str().find("were successfully fetched") == TString::npos, l->Stream.Str()); + } + + Y_UNIT_TEST(Updater_badConfig) { + NTvmApi::TClientSettings s; + UNIT_ASSERT_EXCEPTION(NTvmApi::TThreadedUpdater::Create(s, TDevNullLogger::IAmBrave()), yexception); + s.SelfTvmId = 100500; + UNIT_ASSERT_EXCEPTION(NTvmApi::TThreadedUpdater::Create(s, TDevNullLogger::IAmBrave()), yexception); + s.DiskCacheDir = GetCachePath(); + UNIT_ASSERT_EXCEPTION(NTvmApi::TThreadedUpdater::Create(s, TDevNullLogger::IAmBrave()), yexception); + } + + class TOfflineUpdater: public NTvmApi::TThreadedUpdater { + bool Enabled_; + TString PublicKeys_; + + public: + TOfflineUpdater(const NTvmApi::TClientSettings& settings, + TIntrusivePtr<TLogger> l, + bool enabled = false, + TString keys = NUnittest::TVMKNIFE_PUBLIC_KEYS) + : NTvmApi::TThreadedUpdater(settings, l) + , Enabled_(enabled) + , PublicKeys_(keys) + { + Init(); + StartWorker(); + } + + NUtils::TFetchResult FetchServiceTicketsFromHttp(const TString&) const override { + if (!Enabled_) { + throw yexception() << "alarm"; + } + return {200, {}, "/2/ticket", TVM_RESPONSE, ""}; + } + + NUtils::TFetchResult FetchPublicKeysFromHttp() const override { + if (!Enabled_) { + throw yexception() << "alarm"; + } + return {200, {}, "/2/keys", PublicKeys_, ""}; + } + }; + + Y_UNIT_TEST(StartWithoutCache) { + NTvmApi::TClientSettings s{ + .SelfTvmId = 100500, + .Secret = (TStringBuf) "qwerty", + .FetchServiceTicketsForDstsWithAliases = {{"blackbox", 19}, {"kolmo", 213}}, + .CheckServiceTickets = true, + }; + + auto l = MakeIntrusive<TLogger>(); + UNIT_ASSERT_EXCEPTION_CONTAINS(TOfflineUpdater(s, l), + TRetriableException, + "Failed to start TvmClient. You can retry:"); + + UNIT_ASSERT_VALUES_EQUAL( + TStringBuilder() + << "6: Disk cache disabled. Please set disk cache directory in settings for best reliability\n" + << "4: Failed to get ServiceTickets: alarm\n" + << "4: Failed to get ServiceTickets: alarm\n" + << "4: Failed to get ServiceTickets: alarm\n" + << "4: Failed to update service tickets: alarm\n" + << "3: Service tickets have not been refreshed for too long period\n", + l->Stream.Str()); + } + + static void WriteFile(TString name, TStringBuf body, TInstant time) { + NFs::Remove(CACHE_DIR + name); + TFileOutput f(CACHE_DIR + name); + f << TDiskWriter::PrepareData(time, body); + } + + Y_UNIT_TEST(StartWithOldCache) { + CleanCache(); + WriteFile("./public_keys", + NUnittest::TVMKNIFE_PUBLIC_KEYS, + TInstant::Now() - TDuration::Days(30)); // too old + WriteFile("./service_tickets", + R"({"19":{"ticket":"3:serv:CBAQACIGCJSRBhAL:Fi"}})" + "\t100500", + TInstant::Now()); // too old + + NTvmApi::TClientSettings s{ + .DiskCacheDir = CACHE_DIR, + .SelfTvmId = 100500, + .Secret = (TStringBuf) "qwerty", + .FetchServiceTicketsForDstsWithAliases = {{"blackbox", 19}, {"kolmo", 213}}, + .CheckServiceTickets = true, + }; + + auto l = MakeIntrusive<TLogger>(); + { + TOfflineUpdater u(s, l, true); + UNIT_ASSERT_VALUES_EQUAL(TClientStatus::Ok, u.GetStatus()); + } + + UNIT_ASSERT_C(l->Stream.Str().find("Disk cache (public keys) is too old") != TString::npos, l->Stream.Str()); + UNIT_ASSERT_C(l->Stream.Str().find("Disk cache (service tickets) is too old") != TString::npos, l->Stream.Str()); + UNIT_ASSERT_C(l->Stream.Str().find("were successfully fetched") != TString::npos, l->Stream.Str()); + } + + Y_UNIT_TEST(StartWithMissingCache) { + NTvmApi::TClientSettings s{ + .DiskCacheDir = "../", + .SelfTvmId = 100500, + .CheckServiceTickets = true, + }; + + auto l = MakeIntrusive<TLogger>(); + UNIT_ASSERT_EXCEPTION_CONTAINS(TOfflineUpdater(s, l), + TRetriableException, + "Failed to start TvmClient. You can retry: "); + + UNIT_ASSERT_C(l->Stream.Str().find("does not exist") != TString::npos, l->Stream.Str()); + UNIT_ASSERT_C(l->Stream.Str().find("were successfully fetched") == TString::npos, l->Stream.Str()); + } + + Y_UNIT_TEST(StartWithBadCache_Tickets) { + CleanCache(); + WriteFile("./service_tickets", + TVM_RESPONSE, + TInstant::Now()); + + NTvmApi::TClientSettings s{ + .DiskCacheDir = CACHE_DIR, + .SelfTvmId = 100500, + .Secret = (TStringBuf) "qwerty", + .FetchServiceTicketsForDstsWithAliases = {{"blackbox", 19}, {"kolmo", 213}}, + }; + + auto l = MakeIntrusive<TLogger>(); + { + TOfflineUpdater u(s, l, true); + UNIT_ASSERT_VALUES_EQUAL(TClientStatus::Ok, u.GetStatus()); + } + + UNIT_ASSERT_VALUES_EQUAL( + TStringBuilder() + << "6: File './tmp/service_tickets' was successfully read\n" + << "4: Failed to read service tickets from disk: YYYYYYYYYYYYYYY\n" + << "7: File './tmp/retry_settings' does not exist\n" + << "7: Response with service tickets for 2 destination(s) was successfully fetched from https://tvm-api.yandex.net\n" + << "7: Got responses with service tickets with 1 pages for 2 destination(s)\n" + << "6: Cache was updated with 2 service ticket(s): XXXXXXXXXXX\n" + << "6: File './tmp/service_tickets' was successfully written\n" + << "7: Thread-worker started\n" + << "7: Thread-worker stopped\n", + std::regex_replace(std::regex_replace(std::string(l->Stream.Str()), TIME_REGEX, "XXXXXXXXXXX"), + std::regex(R"(Failed to read service tickets from disk: [^\n]+)"), + "Failed to read service tickets from disk: YYYYYYYYYYYYYYY")); + } + + Y_UNIT_TEST(StartWithBadCache_PublicKeys) { + CleanCache(); + WriteFile("./public_keys", + "ksjdafnlskdjzfgbhdl", + TInstant::Now()); + + NTvmApi::TClientSettings s{ + .DiskCacheDir = CACHE_DIR, + .SelfTvmId = 100500, + .CheckServiceTickets = true, + }; + + auto l = MakeIntrusive<TLogger>(); + UNIT_ASSERT_EXCEPTION_CONTAINS(TOfflineUpdater(s, l), + TRetriableException, + "Failed to start TvmClient. You can retry:"); + + UNIT_ASSERT_C(l->Stream.Str().find("4: Failed to read public keys from disk: Malformed TVM keys") != TString::npos, l->Stream.Str()); + } + + Y_UNIT_TEST(StartWithCacheForAnotherTvmId) { + CleanCache(); + WriteFile("./service_tickets", + TVM_RESPONSE + "\t" + "100499", + TInstant::Now()); + + NTvmApi::TClientSettings s{ + .DiskCacheDir = CACHE_DIR, + .SelfTvmId = 100500, + .Secret = (TStringBuf) "qwerty", + .FetchServiceTicketsForDstsWithAliases = {{"blackbox", 19}, {"kolmo", 213}}, + }; + + auto l = MakeIntrusive<TLogger>(); + { + TOfflineUpdater u(s, l, true); + UNIT_ASSERT_VALUES_EQUAL(TClientStatus::Ok, u.GetStatus()); + } + + UNIT_ASSERT_VALUES_EQUAL( + TStringBuilder() + << "6: File './tmp/service_tickets' was successfully read\n" + << "4: Disk cache is for another tvmId (100499). Self=100500\n" + << "7: File './tmp/retry_settings' does not exist\n" + << "7: Response with service tickets for 2 destination(s) was successfully fetched from https://tvm-api.yandex.net\n" + << "7: Got responses with service tickets with 1 pages for 2 destination(s)\n" + << "6: Cache was updated with 2 service ticket(s): XXXXXXXXXXX\n" + << "6: File './tmp/service_tickets' was successfully written\n" + << "7: Thread-worker started\n" + << "7: Thread-worker stopped\n", + std::regex_replace(std::string(l->Stream.Str()), TIME_REGEX, "XXXXXXXXXXX")); + } + + Y_UNIT_TEST(StartWithCacheForAnotherDsts) { + CleanCache(); + TInstant now = TInstant::Now(); + WriteFile("./service_tickets", + R"({"213" : { "ticket" : "3:serv:CBAQ__________9_IgYIlJEGEAs:T-"}})" + "\t" + "100500", + now); + + NTvmApi::TClientSettings s{ + .DiskCacheDir = CACHE_DIR, + .SelfTvmId = 100500, + .Secret = (TStringBuf) "qwerty", + .FetchServiceTicketsForDstsWithAliases = {{"blackbox", 19}, {"kolmo", 213}}, + }; + + auto l = MakeIntrusive<TLogger>(); + { + TOfflineUpdater u(s, l, true); + auto cache = u.GetCachedServiceTickets(); + UNIT_ASSERT(cache->TicketsById.contains(213)); + UNIT_ASSERT(cache->TicketsById.contains(19)); + UNIT_ASSERT_VALUES_EQUAL(TClientStatus::Ok, u.GetStatus()); + } + UNIT_ASSERT_VALUES_EQUAL( + TStringBuilder() + << "6: File './tmp/service_tickets' was successfully read\n" + << "6: Got 1 service ticket(s) from disk\n" + << "6: Cache was updated with 1 service ticket(s): " << TInstant::Seconds(now.Seconds()) << "\n" + << "7: File './tmp/retry_settings' does not exist\n" + << "7: Response with service tickets for 1 destination(s) was successfully fetched from https://tvm-api.yandex.net\n" + << "7: Got responses with service tickets with 1 pages for 1 destination(s)\n" + << "6: Cache was partly updated with 1 service ticket(s). total: 2\n" + << "6: File './tmp/service_tickets' was successfully written\n" + << "7: Thread-worker started\n" + << "7: Thread-worker stopped\n", + l->Stream.Str()); + l->Stream.Clear(); + + { + TOfflineUpdater u(s, l, true); + auto cache = u.GetCachedServiceTickets(); + UNIT_ASSERT(cache->TicketsById.contains(213)); + UNIT_ASSERT(cache->TicketsById.contains(19)); + UNIT_ASSERT_VALUES_EQUAL(TClientStatus::Ok, u.GetStatus()); + } + UNIT_ASSERT_VALUES_EQUAL( + TStringBuilder() + << "6: File './tmp/service_tickets' was successfully read\n" + << "4: tvm_id in cache is not integer: deprecated\n" + << "6: Got 3 service ticket(s) from disk\n" + << "6: Cache was updated with 2 service ticket(s): XXXXXXXXXXX\n" + << "7: File './tmp/retry_settings' does not exist\n" + << "7: Thread-worker started\n" + << "7: Thread-worker stopped\n", + std::regex_replace(std::string(l->Stream.Str()), TIME_REGEX, "XXXXXXXXXXX")); + } + + Y_UNIT_TEST(StartWithNotFreshCacheForAnotherDsts) { + CleanCache(); + TInstant now = TInstant::Now(); + WriteFile("./service_tickets", + R"({"213" : { "ticket" : "3:serv:CBAQ__________9_IgYIlJEGEAs:T-"}})" + "\t" + "100500", + now - TDuration::Hours(2)); + + NTvmApi::TClientSettings s{ + .DiskCacheDir = CACHE_DIR, + .SelfTvmId = 100500, + .Secret = (TStringBuf) "qwerty", + .FetchServiceTicketsForDstsWithAliases = {{"blackbox", 19}, {"kolmo", 213}}, + }; + + auto l = MakeIntrusive<TLogger>(); + { + TOfflineUpdater u(s, l, true); + auto cache = u.GetCachedServiceTickets(); + UNIT_ASSERT(cache->TicketsById.contains(213)); + UNIT_ASSERT(cache->TicketsById.contains(19)); + UNIT_ASSERT_VALUES_EQUAL(TClientStatus::Ok, u.GetStatus()); + } + UNIT_ASSERT_VALUES_EQUAL( + TStringBuilder() + << "6: File './tmp/service_tickets' was successfully read\n" + << "6: Got 1 service ticket(s) from disk\n" + << "6: Cache was updated with 1 service ticket(s): XXXXXXXXXXX\n" + << "7: File './tmp/retry_settings' does not exist\n" + << "7: Response with service tickets for 2 destination(s) was successfully fetched from https://tvm-api.yandex.net\n" + << "7: Got responses with service tickets with 1 pages for 2 destination(s)\n" + << "6: Cache was updated with 2 service ticket(s): XXXXXXXXXXX\n" + << "6: File './tmp/service_tickets' was successfully written\n" + << "7: Thread-worker started\n" + << "7: Thread-worker stopped\n", + std::regex_replace(std::string(l->Stream.Str()), TIME_REGEX, "XXXXXXXXXXX")); + l->Stream.Clear(); + + { + TOfflineUpdater u(s, l, true); + auto cache = u.GetCachedServiceTickets(); + UNIT_ASSERT(cache->TicketsById.contains(213)); + UNIT_ASSERT(cache->TicketsById.contains(19)); + UNIT_ASSERT_VALUES_EQUAL(TClientStatus::Ok, u.GetStatus()); + } + UNIT_ASSERT_VALUES_EQUAL( + TStringBuilder() + << "6: File './tmp/service_tickets' was successfully read\n" + << "4: tvm_id in cache is not integer: deprecated\n" + << "6: Got 3 service ticket(s) from disk\n" + << "6: Cache was updated with 2 service ticket(s): XXXXXXXXXXX\n" + << "7: File './tmp/retry_settings' does not exist\n" + << "7: Thread-worker started\n" + << "7: Thread-worker stopped\n", + std::regex_replace(std::string(l->Stream.Str()), TIME_REGEX, "XXXXXXXXXXX")); + } + + Y_UNIT_TEST(StartWithPartialDiskCache) { + CleanCache(); + WriteFile("./public_keys", + NUnittest::TVMKNIFE_PUBLIC_KEYS, + TInstant::Now()); + + NTvmApi::TClientSettings s{ + .DiskCacheDir = CACHE_DIR, + .SelfTvmId = 100500, + .Secret = (TStringBuf) "qwerty", + .FetchServiceTicketsForDstsWithAliases = {{"blackbox", 19}, {"kolmo", 213}}, + .CheckServiceTickets = true, + }; + + auto l = MakeIntrusive<TLogger>(); + { + TOfflineUpdater u(s, l, true); + UNIT_ASSERT_VALUES_EQUAL(TClientStatus::Ok, u.GetStatus()); + } + + UNIT_ASSERT_VALUES_EQUAL( + TStringBuilder() + << "7: File './tmp/service_tickets' does not exist\n" + << "6: File './tmp/public_keys' was successfully read\n" + << "6: Cache was updated with public keys: XXXXXXXXXXX\n" + << "7: File './tmp/retry_settings' does not exist\n" + << "7: Response with service tickets for 2 destination(s) was successfully fetched from https://tvm-api.yandex.net\n" + << "7: Got responses with service tickets with 1 pages for 2 destination(s)\n" + << "6: Cache was updated with 2 service ticket(s): XXXXXXXXXXX\n" + << "6: File './tmp/service_tickets' was successfully written\n" + << "7: Thread-worker started\n" + << "7: Thread-worker stopped\n", + std::regex_replace(std::string(l->Stream.Str()), TIME_REGEX, "XXXXXXXXXXX")); + } + + Y_UNIT_TEST(StartFromHttpAndRestartFromDisk) { + CleanCache(); + + NTvmApi::TClientSettings s{ + .DiskCacheDir = CACHE_DIR, + .SelfTvmId = 100500, + .Secret = (TStringBuf) "qwerty", + .FetchServiceTicketsForDstsWithAliases = {{"blackbox", 19}}, + .CheckServiceTickets = true, + .CheckUserTicketsWithBbEnv = EBlackboxEnv::Test, + }; + + { + auto l = MakeIntrusive<TLogger>(); + { + TOfflineUpdater u(s, l, true); + UNIT_ASSERT_VALUES_EQUAL(TClientStatus::Ok, u.GetStatus()); + } + + UNIT_ASSERT_VALUES_EQUAL( + TStringBuilder() + << "7: File './tmp/service_tickets' does not exist\n" + << "7: File './tmp/public_keys' does not exist\n" + << "7: File './tmp/retry_settings' does not exist\n" + << "7: Response with service tickets for 1 destination(s) was successfully fetched from https://tvm-api.yandex.net\n" + << "7: Got responses with service tickets with 1 pages for 1 destination(s)\n" + << "6: Cache was updated with 1 service ticket(s): XXXXXXXXXXX\n" + << "6: File './tmp/service_tickets' was successfully written\n" + << "7: Public keys were successfully fetched from https://tvm-api.yandex.net\n" + << "6: Cache was updated with public keys: XXXXXXXXXXX\n" + << "6: File './tmp/public_keys' was successfully written\n" + << "7: Thread-worker started\n" + << "7: Thread-worker stopped\n", + std::regex_replace(std::string(l->Stream.Str()), TIME_REGEX, "XXXXXXXXXXX")); + } + + { + auto l = MakeIntrusive<TLogger>(); + { + TOfflineUpdater u(s, l, true); + UNIT_ASSERT_VALUES_EQUAL(TClientStatus::Ok, u.GetStatus()); + } + + UNIT_ASSERT_VALUES_EQUAL( + TStringBuilder() + << "6: File './tmp/service_tickets' was successfully read\n" + << "4: tvm_id in cache is not integer: deprecated\n" + << "6: Got 3 service ticket(s) from disk\n" + << "6: Cache was updated with 1 service ticket(s): XXXXXXXXXXX\n" + << "6: File './tmp/public_keys' was successfully read\n" + << "6: Cache was updated with public keys: XXXXXXXXXXX\n" + << "7: File './tmp/retry_settings' does not exist\n" + << "7: Thread-worker started\n" + << "7: Thread-worker stopped\n", + std::regex_replace(std::string(l->Stream.Str()), TIME_REGEX, "XXXXXXXXXXX")); + } + } + + class TUnstableUpdater: public NTvmApi::TThreadedUpdater { + mutable int V1_ = 0; + mutable int V2_ = 0; + + public: + TUnstableUpdater(const NTvmApi::TClientSettings& settings, TIntrusivePtr<TLogger> l) + : NTvmApi::TThreadedUpdater(settings, l) + { + UNIT_ASSERT_NO_EXCEPTION_C(Init(), l->Stream.Str()); + ExpBackoff_.SetEnabled(false); + StartWorker(); + + UNIT_ASSERT_VALUES_EQUAL_C(TClientStatus::Ok, GetStatus(), l->Stream.Str()); + + Sleep(TDuration::MicroSeconds(100)); + PublicKeysDurations_.Expiring = TDuration::MicroSeconds(100); + UNIT_ASSERT_VALUES_EQUAL_C(TClientStatus(TClientStatus::Warning, "Internal client error: failed to collect last useful error message, please report this message to tvm-dev@yandex-team.ru"), + GetStatus(), + l->Stream.Str()); + + PublicKeysDurations_.Invalid = TDuration::MicroSeconds(20); + UNIT_ASSERT_VALUES_EQUAL_C(TClientStatus::Error, GetStatus(), l->Stream.Str()); + + PublicKeysDurations_.Expiring = TDuration::Seconds(100); + PublicKeysDurations_.Invalid = TDuration::Seconds(200); + UNIT_ASSERT_VALUES_EQUAL_C(TClientStatus::Ok, GetStatus(), l->Stream.Str()); + + ServiceTicketsDurations_.Expiring = TDuration::MicroSeconds(100); + UNIT_ASSERT_VALUES_EQUAL_C(TClientStatus::Warning, GetStatus(), l->Stream.Str()); + + ServiceTicketsDurations_.Invalid = TDuration::MicroSeconds(20); + UNIT_ASSERT_VALUES_EQUAL_C(TClientStatus::Warning, GetStatus(), l->Stream.Str()); + + const TInstant* inv = &GetCachedServiceTickets()->InvalidationTime; + *const_cast<TInstant*>(inv) = TInstant::Now() + TDuration::Seconds(30); + UNIT_ASSERT_VALUES_EQUAL_C(TClientStatus::Error, GetStatus(), l->Stream.Str()); + } + + NUtils::TFetchResult FetchServiceTicketsFromHttp(const TString&) const override { + Y_ENSURE_EX(++V1_ > 1, yexception() << "++v1_ > 1:" << V1_); + return {200, {}, "/2/ticket", TVM_RESPONSE, ""}; + } + + NUtils::TFetchResult FetchPublicKeysFromHttp() const override { + Y_ENSURE_EX(++V2_ > 2, yexception() << "++v2_ > 2:" << V2_); + return {200, {}, "/2/keys", NUnittest::TVMKNIFE_PUBLIC_KEYS, ""}; + } + }; + + Y_UNIT_TEST(StartFromUnstableHttp) { + CleanCache(); + + NTvmApi::TClientSettings s{ + .DiskCacheDir = CACHE_DIR, + .SelfTvmId = 100500, + .Secret = (TStringBuf) "qwerty", + .FetchServiceTicketsForDstsWithAliases = {{"blackbox", 19}}, + .CheckServiceTickets = true, + .CheckUserTicketsWithBbEnv = EBlackboxEnv::Test, + }; + + auto l = MakeIntrusive<TLogger>(); + { + TUnstableUpdater u(s, l); + } + + UNIT_ASSERT_C(l->Stream.Str().Contains("++v1_ > 1"), l->Stream.Str()); + UNIT_ASSERT_C(l->Stream.Str().Contains("++v2_ > 2"), l->Stream.Str()); + UNIT_ASSERT_C(l->Stream.Str().Contains("7: Response with service tickets for 1 destination(s) was successfully fetched from https://tvm-api.yandex.net"), l->Stream.Str()); + UNIT_ASSERT_C(l->Stream.Str().Contains("7: Public keys were successfully fetched"), l->Stream.Str()); + } + + Y_UNIT_TEST(GetUpdateTimeOfServiceTickets) { + CleanCache(); + TInstant ins = TInstant::Now(); + WriteFile("./service_tickets", + TVM_RESPONSE + "\t" + "100500", + ins); + + NTvmApi::TClientSettings s{ + .DiskCacheDir = CACHE_DIR, + .SelfTvmId = 100500, + .Secret = (TStringBuf) "qwerty", + .FetchServiceTicketsForDstsWithAliases = {{"blackbox", 19}}, + }; + + auto l = MakeIntrusive<TLogger>(); + TOfflineUpdater u(s, l, true); + UNIT_ASSERT_VALUES_EQUAL(TClientStatus::Ok, u.GetStatus()); + UNIT_ASSERT_VALUES_EQUAL(TInstant(), u.GetUpdateTimeOfPublicKeys()); + UNIT_ASSERT_VALUES_EQUAL(TInstant::Seconds(ins.Seconds()), u.GetUpdateTimeOfServiceTickets()); + } + + class TSignalingUpdater: public NTvmApi::TThreadedUpdater { + mutable int V_ = 0; + TAutoEvent& Ev_; + const TStringBuf PublicKeys_; + + public: + TSignalingUpdater(const NTvmApi::TClientSettings& settings, + TLoggerPtr l, + TAutoEvent& ev, + const TStringBuf keys = NUnittest::TVMKNIFE_PUBLIC_KEYS) + : NTvmApi::TThreadedUpdater(settings, l) + , Ev_(ev) + , PublicKeys_(keys) + { + WorkerAwakingPeriod_ = TDuration::MilliSeconds(300); + PublicKeysDurations_.RefreshPeriod = TDuration::MilliSeconds(700); + Init(); + ExpBackoff_.SetEnabled(false); + StartWorker(); + } + + NUtils::TFetchResult FetchPublicKeysFromHttp() const override { + if (++V_ >= 2) { + Ev_.Signal(); + } + return {200, {}, "/2/keys", TString(PublicKeys_), ""}; + } + }; + + Y_UNIT_TEST(StartWorker) { + class TSignalingUpdater: public NTvmApi::TThreadedUpdater { + mutable int V_ = 0; + TAutoEvent& Ev_; + + public: + TSignalingUpdater(const NTvmApi::TClientSettings& settings, TLoggerPtr l, TAutoEvent& ev) + : NTvmApi::TThreadedUpdater(settings, l) + , Ev_(ev) + { + WorkerAwakingPeriod_ = TDuration::MilliSeconds(300); + PublicKeysDurations_.RefreshPeriod = TDuration::MilliSeconds(700); + Init(); + ExpBackoff_.SetEnabled(false); + StartWorker(); + } + + void Worker() override { + NTvmApi::TThreadedUpdater::Worker(); + Ev_.Signal(); + } + + NUtils::TFetchResult FetchPublicKeysFromHttp() const override { + if (++V_ < 4) { + return {500, {}, "/2/keys", "lol", ""}; + } + return {200, {}, "/2/keys", NUnittest::TVMKNIFE_PUBLIC_KEYS, "CAEQChkAAAAAAAD4PyGamZmZmZm5PyhkMAE4B0BGSAI"}; + } + }; + + CleanCache(); + TInstant expiringPubKeys = TInstant::Now() - TDuration::Days(3); + WriteFile("./public_keys", NUnittest::TVMKNIFE_PUBLIC_KEYS, expiringPubKeys); + + NTvmApi::TClientSettings s{ + .DiskCacheDir = CACHE_DIR, + .SelfTvmId = 100500, + .CheckServiceTickets = true, + }; + + auto l = MakeIntrusive<TLogger>(); + TAutoEvent ev; + { + TSignalingUpdater u(s, l, ev); + UNIT_ASSERT_VALUES_EQUAL(TClientStatus(TClientStatus::Warning, "PublicKeys: Path:/2/keys.Code=500: lol"), + u.GetStatus()); + UNIT_ASSERT_VALUES_EQUAL(TInstant::Seconds(expiringPubKeys.Seconds()), u.GetUpdateTimeOfPublicKeys()); + UNIT_ASSERT_VALUES_EQUAL(TInstant(), u.GetUpdateTimeOfServiceTickets()); + + UNIT_ASSERT(ev.WaitT(TDuration::Seconds(15))); + Sleep(TDuration::MilliSeconds(500)); + UNIT_ASSERT_VALUES_EQUAL(TClientStatus::Ok, u.GetStatus()); + } + + UNIT_ASSERT_VALUES_EQUAL( + TStringBuilder() + << "6: File './tmp/public_keys' was successfully read\n" + << "6: Cache was updated with public keys: XXXXXXXXXXX\n" + << "7: File './tmp/retry_settings' does not exist\n" + << "4: Failed to get PublicKeys: Path:/2/keys.Code=500: lol\n" + << "4: Failed to get PublicKeys: Path:/2/keys.Code=500: lol\n" + << "4: Failed to get PublicKeys: Path:/2/keys.Code=500: lol\n" + << "4: Failed to update public keys: Path:/2/keys.Code=500: lol\n" + << "3: Public keys have not been refreshed for too long period\n" + << "7: Thread-worker started\n" + << "7: Retry settings were updated: exponential_backoff_min:0.000000s->1.000000s;exponential_backoff_max:60.000000s->10.000000s;exponential_backoff_factor:2->1.5;exponential_backoff_jitter:0.5->0.1;max_random_sleep_default:5.000000s->0.100000s;retries_on_start:3->1;worker_awaking_period:10.000000s->7.000000s;dsts_limit:300->70;\n" + << "6: File './tmp/retry_settings' was successfully written\n" + << "7: Public keys were successfully fetched from https://tvm-api.yandex.net\n" + << "6: Cache was updated with public keys: XXXXXXXXXXX\n" + << "6: File './tmp/public_keys' was successfully written\n" + << "7: Thread-worker stopped\n", + std::regex_replace(std::string(l->Stream.Str()), TIME_REGEX, "XXXXXXXXXXX")); + } + +#if defined(_unix_) + Y_UNIT_TEST(StartFromCacheAndBadPublicKeysFromHttp) { + CleanCache(); + TInstant now = TInstant::Now(); + WriteFile("public_keys", NUnittest::TVMKNIFE_PUBLIC_KEYS, now - TDuration::Days(3)); // expiring public keys + + NTvmApi::TClientSettings s{ + .DiskCacheDir = CACHE_DIR, + .SelfTvmId = 100500, + .CheckServiceTickets = true, + }; + + auto l = MakeIntrusive<TLogger>(); + { + TAutoEvent ev; + TSignalingUpdater u(s, l, ev, "malformed keys"); + UNIT_ASSERT_VALUES_EQUAL(TClientStatus(TClientStatus::Warning, "PublicKeys: Malformed TVM keys"), + u.GetStatus()); + + UNIT_ASSERT(ev.WaitT(TDuration::Seconds(15))); + UNIT_ASSERT_VALUES_EQUAL(TClientStatus::Warning, u.GetStatus()); + } + + UNIT_ASSERT_VALUES_EQUAL( + TStringBuilder() + << "6: File './tmp/public_keys' was successfully read\n" + << "6: Cache was updated with public keys: " << TInstant::Seconds((now - TDuration::Days(3)).Seconds()) << "\n" + << "7: File './tmp/retry_settings' does not exist\n" + << "7: Public keys were successfully fetched from https://tvm-api.yandex.net\n" + << "4: Failed to update public keys: Malformed TVM keys\n" + << "3: Public keys have not been refreshed for too long period\n" + << "7: Thread-worker started\n" + << "7: Public keys were successfully fetched from https://tvm-api.yandex.net\n" + << "4: Failed to update public keys: Malformed TVM keys\n" + << "3: Public keys have not been refreshed for too long period\n" + << "7: Thread-worker stopped\n", + l->Stream.Str()); + } +#endif + + Y_UNIT_TEST(StartWithBadPublicKeysFromHttp) { + NTvmApi::TClientSettings s{ + .SelfTvmId = 100500, + .CheckServiceTickets = true, + }; + + auto l = MakeIntrusive<TLogger>(); + TAutoEvent ev; + UNIT_ASSERT_EXCEPTION_CONTAINS(TOfflineUpdater(s, l, true, "some public keys"), + TRetriableException, + "Failed to start TvmClient. You can retry:"); + UNIT_ASSERT_VALUES_EQUAL( + TStringBuilder() + << "6: Disk cache disabled. Please set disk cache directory in settings for best reliability\n" + << "7: Public keys were successfully fetched from https://tvm-api.yandex.net\n" + << "4: Failed to update public keys: Malformed TVM keys\n" + << "3: Public keys have not been refreshed for too long period\n", + l->Stream.Str()); + } + + class TNotInitedUpdater: public NTvmApi::TThreadedUpdater { + public: + TNotInitedUpdater(const NTvmApi::TClientSettings& settings, TLoggerPtr l = TDevNullLogger::IAmBrave()) + : NTvmApi::TThreadedUpdater(settings, l) + { + this->ExpBackoff_.SetEnabled(false); + } + + using NTvmApi::TThreadedUpdater::AppendToJsonArray; + using NTvmApi::TThreadedUpdater::AreServicesTicketsOk; + using NTvmApi::TThreadedUpdater::CreateJsonArray; + using NTvmApi::TThreadedUpdater::FindMissingDsts; + using NTvmApi::TThreadedUpdater::GetPublicKeysFromHttp; + using NTvmApi::TThreadedUpdater::GetServiceTicketsFromHttp; + using NTvmApi::TThreadedUpdater::Init; + using NTvmApi::TThreadedUpdater::IsServiceContextOk; + using NTvmApi::TThreadedUpdater::IsTimeToUpdatePublicKeys; + using NTvmApi::TThreadedUpdater::IsTimeToUpdateServiceTickets; + using NTvmApi::TThreadedUpdater::IsUserContextOk; + using NTvmApi::TThreadedUpdater::ParseTicketsFromDisk; + using NTvmApi::TThreadedUpdater::ParseTicketsFromResponse; + using NTvmApi::TThreadedUpdater::PrepareRequestForServiceTickets; + using NTvmApi::TThreadedUpdater::PrepareTicketsForDisk; + using NTvmApi::TThreadedUpdater::SetServiceContext; + using NTvmApi::TThreadedUpdater::SetServiceTickets; + using NTvmApi::TThreadedUpdater::SetUserContext; + using NTvmApi::TThreadedUpdater::THttpResult; + using NTvmApi::TThreadedUpdater::TPairTicketsErrors; + using TAsyncUpdaterBase::IsServiceTicketMapOk; + }; + + Y_UNIT_TEST(IsCacheComplete_Empty) { + NTvmApi::TClientSettings s{ + .SelfTvmId = 100500, + .Secret = (TStringBuf) "qwerty", + .FetchServiceTicketsForDstsWithAliases = {{"blackbox", 19}, {"blackbox2", 20}}, + .CheckServiceTickets = true, + .CheckUserTicketsWithBbEnv = EBlackboxEnv::Test, + }; + + TNotInitedUpdater u(s); + UNIT_ASSERT(!u.AreServicesTicketsOk()); + } + + Y_UNIT_TEST(IsCacheComplete_Tickets) { + NTvmApi::TClientSettings s{ + .SelfTvmId = 100500, + .Secret = (TStringBuf) "qwerty", + .FetchServiceTicketsForDstsWithAliases = {{"blackbox", 19}, {"blackbox2", 20}}, + }; + + TNotInitedUpdater u(s); + UNIT_ASSERT(!u.AreServicesTicketsOk()); + + u.SetServiceTickets(MakeIntrusiveConst<TServiceTickets>( + TServiceTickets::TMapIdStr({{1, "mega_ticket"}}), + TServiceTickets::TMapIdStr({{2, "mega_error"}}), + TServiceTickets::TMapAliasId())); + UNIT_ASSERT(!u.AreServicesTicketsOk()); + + u.SetServiceTickets(MakeIntrusiveConst<TServiceTickets>( + TServiceTickets::TMapIdStr({ + {1, "mega_ticket"}, + {2, "mega_ticket2"}, + }), + TServiceTickets::TMapIdStr({ + {3, "mega_error3"}, + }), + TServiceTickets::TMapAliasId())); + UNIT_ASSERT(u.AreServicesTicketsOk()); + } + + Y_UNIT_TEST(IsCacheComplete_Service) { + NTvmApi::TClientSettings s{ + .SelfTvmId = 100500, + .CheckServiceTickets = true, + }; + + TNotInitedUpdater u(s); + UNIT_ASSERT(!u.IsServiceContextOk()); + + u.SetServiceContext(MakeIntrusiveConst<TServiceContext>( + TServiceContext::CheckingFactory(100500, NUnittest::TVMKNIFE_PUBLIC_KEYS))); + UNIT_ASSERT(u.IsServiceContextOk()); + } + + Y_UNIT_TEST(IsCacheComplete_User) { + NTvmApi::TClientSettings s{ + .SelfTvmId = 100500, + .CheckUserTicketsWithBbEnv = EBlackboxEnv::Test, + }; + + TNotInitedUpdater u(s); + UNIT_ASSERT(!u.IsUserContextOk()); + + u.SetUserContext(NUnittest::TVMKNIFE_PUBLIC_KEYS); + UNIT_ASSERT(u.IsUserContextOk()); + } + + Y_UNIT_TEST(TicketsOnDisk) { + TString res = R"({ + "19" : { "ticket" : "3:serv:CBAQ__________9_IgYIKhCUkQY:CX"}, + "213" : { "ticket" : "service_ticket_2"}, + "234" : { "error" : "Dst is not found" }, + "185" : { "ticket" : "service_ticket_3"}, + "deprecated" : { "ticket" : "deprecated_ticket" } + })"; + res.append("\t100500"); + + UNIT_ASSERT_VALUES_EQUAL(res, TNotInitedUpdater::PrepareTicketsForDisk(TVM_RESPONSE, 100500)); + + auto pair = TNotInitedUpdater::ParseTicketsFromDisk(res); + UNIT_ASSERT_VALUES_EQUAL(pair.first, TVM_RESPONSE); + UNIT_ASSERT_VALUES_EQUAL(pair.second, 100500); + + res.push_back('a'); + UNIT_ASSERT_EXCEPTION(TNotInitedUpdater::ParseTicketsFromDisk(res), yexception); + } + + Y_UNIT_TEST(IsTimeToUpdatePublicKeys) { + NTvmApi::TClientSettings s{ + .CheckUserTicketsWithBbEnv = EBlackboxEnv::Test, + }; + + TNotInitedUpdater u(s); + + UNIT_ASSERT(!u.IsTimeToUpdatePublicKeys(TInstant::Now())); + UNIT_ASSERT(!u.IsTimeToUpdatePublicKeys(TInstant::Now() - TDuration::Hours(23))); + UNIT_ASSERT(u.IsTimeToUpdatePublicKeys(TInstant::Now() - TDuration::Days(1) - TDuration::MilliSeconds(1))); + } + + Y_UNIT_TEST(IsTimeToUpdateServiceTickets) { + NTvmApi::TClientSettings s{ + .SelfTvmId = 100500, + .Secret = (TStringBuf) "qwerty", + .FetchServiceTicketsForDstsWithAliases = {{"blackbox", 19}, {"blackbox2", 20}}, + }; + + TNotInitedUpdater u(s); + + UNIT_ASSERT(!u.IsTimeToUpdateServiceTickets(TInstant::Now() - TDuration::Minutes(59))); + UNIT_ASSERT(u.IsTimeToUpdateServiceTickets(TInstant::Now() - TDuration::Hours(1) - TDuration::MilliSeconds(1))); + } + + Y_UNIT_TEST(StartWithIncompliteCache) { + NTvmApi::TClientSettings s{ + .SelfTvmId = 100500, + .Secret = (TStringBuf) "qwerty", + .FetchServiceTicketsForDstsWithAliases = {{"blackbox", 19}, {"blackbox2", 20}}, + .CheckServiceTickets = true, + .CheckUserTicketsWithBbEnv = EBlackboxEnv::Test, + }; + + auto l = MakeIntrusive<TLogger>(); + UNIT_ASSERT_EXCEPTION_CONTAINS(TOfflineUpdater(s, l, true), + TNonRetriableException, + "Failed to get ServiceTicket for 20: Missing tvm_id in response, should never happend: 20"); + + UNIT_ASSERT_VALUES_EQUAL( + TStringBuilder() + << "6: Disk cache disabled. Please set disk cache directory in settings for best reliability\n" + << "7: Response with service tickets for 2 destination(s) was successfully fetched from https://tvm-api.yandex.net\n" + << "7: Got responses with service tickets with 1 pages for 2 destination(s)\n" + << "3: Failed to get service ticket for dst=20: Missing tvm_id in response, should never happend: 20\n" + << "6: Cache was updated with 1 service ticket(s): XXXXXXXXXXX\n", + std::regex_replace(std::string(l->Stream.Str()), TIME_REGEX, "XXXXXXXXXXX")); + } + + Y_UNIT_TEST(PrepareRequestForServiceTickets) { + const TServiceContext ctx = TServiceContext::SigningFactory("AAAAAAAAAAAAAAAAAAAAAA"); + + TString s = TNotInitedUpdater::PrepareRequestForServiceTickets(117, + ctx, + {19, 20}, + NUtils::TProcInfo{ + "__some_pid__", + "__some_pname__", + "kar", + }, + 100700); + SubstGlobal(s.resize(s.size() - 5), "deb_", ""); + UNIT_ASSERT_VALUES_EQUAL("grant_type=client_credentials&src=117&dst=19,20&ts=100700&sign=XTz2Obd6PII_BHxswzWPJTjju9SrKsN6hyu1VsyxBvU&get_retry_settings=yes&_pid=__some_pid__&_procces_name=__some_pname__&lib_version=client_kar", + s); + + s = TNotInitedUpdater::PrepareRequestForServiceTickets(118, + ctx, + {19}, + NUtils::TProcInfo{ + "__some_pid__", + {}, + "kva_", + }, + 100900); + SubstGlobal(s.resize(s.size() - 5), "deb_", ""); + UNIT_ASSERT_VALUES_EQUAL("grant_type=client_credentials&src=118&dst=19&ts=100900&sign=-trBo9AtBLjp2ihy6cFAdMAQ6S9afHj23rFzYQ32jkQ&get_retry_settings=yes&_pid=__some_pid__&lib_version=client_kva_", + s); + } + + Y_UNIT_TEST(ParseTicketsFromResponse) { + NTvmApi::TClientSettings s{ + .SelfTvmId = 100500, + .CheckServiceTickets = true, + }; + + auto l = MakeIntrusive<TLogger>(); + TNotInitedUpdater u(s, l); + + TNotInitedUpdater::TPairTicketsErrors t; + UNIT_ASSERT_EXCEPTION_CONTAINS(u.ParseTicketsFromResponse("{", NTvmApi::TDstSet{19}, t), + yexception, + "Invalid json from tvm-api"); + + t = {}; + u.ParseTicketsFromResponse(TVM_RESPONSE, NTvmApi::TDstSet{19}, t); + + TNotInitedUpdater::TPairTicketsErrors expected{{{19, "3:serv:CBAQ__________9_IgYIKhCUkQY:CX"}}, {}}; + UNIT_ASSERT_VALUES_EQUAL("6: Disk cache disabled. Please set disk cache directory in settings for best reliability\n", + l->Stream.Str()); + UNIT_ASSERT_EQUAL(expected, t); + + t = {}; + u.ParseTicketsFromResponse(TVM_RESPONSE, + NTvmApi::TDstSet{19, 213, 234, 235}, + t); + expected = {{{19, "3:serv:CBAQ__________9_IgYIKhCUkQY:CX"}, + {213, "service_ticket_2"}}, + {{234, "Dst is not found"}, + {235, "Missing tvm_id in response, should never happend: 235"}}}; + UNIT_ASSERT_EQUAL(expected, t); + UNIT_ASSERT_VALUES_EQUAL("6: Disk cache disabled. Please set disk cache directory in settings for best reliability\n", + l->Stream.Str()); + + t = {}; + u.ParseTicketsFromResponse( + R"([ + {"19" : { "ticket" : "3:serv:CBAQ__________9_IgYIKhCUkQY:CX"},"234" : { "error" : "Dst is not found" }}, + {"213" : { "ticket" : "service_ticket_2"},"185" : { "ticket" : "service_ticket_3"}}, + {"deprecated" : { "ticket" : "deprecated_ticket" }} + ])", + NTvmApi::TDstSet{19, 213, 234, 235}, + t); + expected = {{{19, "3:serv:CBAQ__________9_IgYIKhCUkQY:CX"}, + {213, "service_ticket_2"}}, + {{234, "Dst is not found"}, + {235, "Missing tvm_id in response, should never happend: 235"}}}; + UNIT_ASSERT_EQUAL(expected, t); + UNIT_ASSERT_VALUES_EQUAL("6: Disk cache disabled. Please set disk cache directory in settings for best reliability\n", + l->Stream.Str()); + } + + Y_UNIT_TEST(ParseTicketsFromResponseAsArray) { + NTvmApi::TClientSettings s{ + .SelfTvmId = 100500, + .CheckServiceTickets = true, + }; + + auto l = MakeIntrusive<TLogger>(); + TNotInitedUpdater u(s, l); + + TNotInitedUpdater::TPairTicketsErrors t; + UNIT_ASSERT_EXCEPTION_CONTAINS(u.ParseTicketsFromResponse("[", NTvmApi::TDstSet{19}, t), + yexception, + "Invalid json from tvm-api"); + + u.ParseTicketsFromResponse(R"([])", NTvmApi::TDstSet{19}, t); + UNIT_ASSERT_VALUES_EQUAL("6: Disk cache disabled. Please set disk cache directory in settings for best reliability\n", + l->Stream.Str()); + TNotInitedUpdater::TPairTicketsErrors expected = { + {}, {{19, "Missing tvm_id in response, should never happend: 19"}}}; + UNIT_ASSERT_VALUES_EQUAL(expected, t); + l->Stream.Clear(); + + t = {}; + u.ParseTicketsFromResponse( + R"([{},{"19" : { "ticket" : "3:serv:CBAQ__________9_IgYIKhCUkQY:CX"}}, {"213" : { "ticket" : "service_ticket_2"}}])", + NTvmApi::TDstSet{19}, + t); + UNIT_ASSERT_VALUES_EQUAL("", l->Stream.Str()); + expected = {{{19, "3:serv:CBAQ__________9_IgYIKhCUkQY:CX"}}, {}}; + UNIT_ASSERT_EQUAL(expected, t); + + t = {}; + u.ParseTicketsFromResponse( + R"([{ + "19" : { "ticket" : "3:serv:CBAQ__________9_IgYIKhCUkQY:CX"} + }, + { + "213" : { "ticket" : "service_ticket_2"}, + "234" : { "error" : "Dst is not found" } + }, + { + "185" : { "ticket" : "service_ticket_3"}, + "deprecated" : { "ticket" : "deprecated_ticket" } + } + ])", + NTvmApi::TDstSet{19, 213, 234, 235}, + t); + expected = {{{19, "3:serv:CBAQ__________9_IgYIKhCUkQY:CX"}, + {213, "service_ticket_2"}}, + {{234, "Dst is not found"}, + {235, "Missing tvm_id in response, should never happend: 235"}}}; + UNIT_ASSERT_EQUAL(expected, t); + UNIT_ASSERT_VALUES_EQUAL("", l->Stream.Str()); + } + + class TReplier: public TRequestReplier { + public: + HttpCodes Code = HTTP_OK; + + bool DoReply(const TReplyParams& params) override { + TParsedHttpFull fl(params.Input.FirstLine()); + + THttpResponse resp(Code); + if (fl.Path == "/2/keys") { + resp.SetContent(NUnittest::TVMKNIFE_PUBLIC_KEYS); + } else if (fl.Path == "/2/ticket") { + resp.SetContent(TVM_RESPONSE); + } else { + UNIT_ASSERT(false); + } + resp.OutTo(params.Output); + + return true; + } + }; + + class TOnlineUpdater: public NTvmApi::TThreadedUpdater { + public: + TOnlineUpdater(const NTvmApi::TClientSettings& settings, TIntrusivePtr<TLogger> l) + : NTvmApi::TThreadedUpdater(settings, l) + { + Init(); + ExpBackoff_.SetEnabled(false); + StartWorker(); + } + }; + + Y_UNIT_TEST(MocServerOk) { + TPortManager pm; + ui16 tvmPort = pm.GetPort(80); + NMock::TMockServer server(tvmPort, []() { return new TReplier; }); + + NTvmApi::TClientSettings s{ + .SelfTvmId = 100500, + .Secret = (TStringBuf) "qwerty", + .FetchServiceTicketsForDstsWithAliases = {{"blackbox", 19}}, + .CheckServiceTickets = true, + .CheckUserTicketsWithBbEnv = EBlackboxEnv::Test, + .TvmHost = "http://localhost", + .TvmPort = tvmPort, + }; + + auto l = MakeIntrusive<TLogger>(); + { + TOnlineUpdater u(s, l); + UNIT_ASSERT_VALUES_EQUAL_C(TClientStatus::Ok, u.GetStatus(), l->Stream.Str()); + } + + UNIT_ASSERT_VALUES_EQUAL( + TStringBuilder() + << "6: Disk cache disabled. Please set disk cache directory in settings for best reliability\n" + << "7: Response with service tickets for 1 destination(s) was successfully fetched from http://localhost\n" + << "7: Got responses with service tickets with 1 pages for 1 destination(s)\n" + << "6: Cache was updated with 1 service ticket(s): XXXXXXXXXXX\n" + << "7: Public keys were successfully fetched from http://localhost\n" + << "6: Cache was updated with public keys: XXXXXXXXXXX\n" + << "7: Thread-worker started\n" + << "7: Thread-worker stopped\n", + std::regex_replace(std::string(l->Stream.Str()), TIME_REGEX, "XXXXXXXXXXX")); + } + + Y_UNIT_TEST(MocServerBad) { + TPortManager pm; + ui16 tvmPort = pm.GetPort(80); + NMock::TMockServer server(tvmPort, + []() { + auto p = new TReplier; + p->Code = HTTP_BAD_REQUEST; + return p; + }); + + NTvmApi::TClientSettings s{ + .SelfTvmId = 100500, + .Secret = (TStringBuf) "qwerty", + .FetchServiceTicketsForDstsWithAliases = {{"blackbox", 19}}, + .CheckServiceTickets = true, + .CheckUserTicketsWithBbEnv = EBlackboxEnv::Test, + .TvmHost = "http://localhost", + .TvmPort = tvmPort, + }; + + auto l = MakeIntrusive<TLogger>(); + UNIT_ASSERT_EXCEPTION_CONTAINS_C(TOnlineUpdater(s, l), + TNonRetriableException, + "Failed to start TvmClient. Do not retry: ServiceTickets: Path:/2/ticket.Code=400:", + l->Stream.Str()); + } + + Y_UNIT_TEST(MocServerPaginated) { + class TReplier: public TRequestReplier { + public: + TString Response; + TReplier(TString response) + : Response(response) + { + } + + bool DoReply(const TReplyParams& params) override { + TParsedHttpFull fl(params.Input.FirstLine()); + if (fl.Path != "/2/ticket") { + UNIT_ASSERT_C(false, fl.Path); + } + + THttpResponse resp(HTTP_OK); + resp.SetContent(Response); + resp.OutTo(params.Output); + return true; + } + }; + + TPortManager pm; + ui16 tvmPort = pm.GetPort(80); + TVector<TString> responses = { + R"({"15" : { "ticket" : "service_ticket_3" },"19" : { "ticket" : "3:serv:CBAQ__________9_IgYIKhCUkQY:CX"}})", + R"({"222" : { "ticket" : "service_ticket_2"}, "239" : { "error" : "Dst is not found" }})", + R"({"185" : { "ticket" : "service_ticket_3"}})", + }; + NMock::TMockServer server(tvmPort, [&responses]() { + if (responses.empty()) { + return new TReplier("<NULL>"); + } + TString r = responses.front(); + responses.erase(responses.begin()); + return new TReplier(r); + }); + + NTvmApi::TClientSettings s{ + .SelfTvmId = 100500, + .Secret = (TStringBuf) "qwerty", + .FetchServiceTicketsForDsts = {19, 222, 239, 100500, 15}, + .CheckServiceTickets = true, + .CheckUserTicketsWithBbEnv = EBlackboxEnv::Test, + .TvmHost = "http://localhost", + .TvmPort = tvmPort, + }; + + auto l = MakeIntrusive<TLogger>(); + { + TNotInitedUpdater u(s, l); + TNotInitedUpdater::THttpResult result = u.GetServiceTicketsFromHttp(NTvmApi::TDstSet{19, 222, 239, 100500, 15}, 2); + UNIT_ASSERT_VALUES_EQUAL(TSmallVec<TString>({ + R"({"15" : { "ticket" : "service_ticket_3" },"19" : { "ticket" : "3:serv:CBAQ__________9_IgYIKhCUkQY:CX"}})", + R"({"222" : { "ticket" : "service_ticket_2"}, "239" : { "error" : "Dst is not found" }})", + R"({"185" : { "ticket" : "service_ticket_3"}})", + }), + result.Responses); + TNotInitedUpdater::TPairTicketsErrors expected{ + { + {19, "3:serv:CBAQ__________9_IgYIKhCUkQY:CX"}, + {222, "service_ticket_2"}, + {15, "service_ticket_3"}, + }, + { + {239, "Dst is not found"}, + {100500, "Missing tvm_id in response, should never happend: 100500"}, + }, + }; + UNIT_ASSERT_VALUES_EQUAL(expected, result.TicketsWithErrors); + } + + UNIT_ASSERT_VALUES_EQUAL( + TStringBuilder() + << "6: Disk cache disabled. Please set disk cache directory in settings for best reliability\n" + << "7: Response with service tickets for 2 destination(s) was successfully fetched from http://localhost\n" + << "7: Response with service tickets for 2 destination(s) was successfully fetched from http://localhost\n" + << "7: Response with service tickets for 1 destination(s) was successfully fetched from http://localhost\n" + << "7: Got responses with service tickets with 3 pages for 5 destination(s)\n" + << "3: Failed to get service ticket for dst=100500: Missing tvm_id in response, should never happend: 100500\n" + << "3: Failed to get service ticket for dst=239: Dst is not found\n", + l->Stream.Str()); + } + + Y_UNIT_TEST(FindMissingDsts) { + UNIT_ASSERT_VALUES_EQUAL(NTvmApi::TClientSettings::TDstVector({6, 9}), + TNotInitedUpdater::FindMissingDsts({1, 2, 3, 4}, {1, 4, 6, 9})); + UNIT_ASSERT_VALUES_EQUAL(NTvmApi::TClientSettings::TDstVector(), + TNotInitedUpdater::FindMissingDsts({1, 2, 3, 4, 5, 6, 7, 8, 9}, {1, 4, 6, 9})); + UNIT_ASSERT_VALUES_EQUAL(NTvmApi::TClientSettings::TDstVector({1, 4, 6, 9}), + TNotInitedUpdater::FindMissingDsts(NTvmApi::TDstSet(), {1, 4, 6, 9})); + UNIT_ASSERT_VALUES_EQUAL(NTvmApi::TClientSettings::TDstVector(1, 19), + TNotInitedUpdater::FindMissingDsts({213}, {19, 213})); + + auto make = [](TVector<int> ids) { + TServiceTickets::TMapIdStr m; + for (auto i : ids) { + m.insert({i, ""}); + } + return MakeIntrusiveConst<TServiceTickets>(std::move(m), TServiceTickets::TMapIdStr{}, TServiceTickets::TMapAliasId{}); + }; + + UNIT_ASSERT_VALUES_EQUAL(NTvmApi::TClientSettings::TDstVector({6, 9}), + TNotInitedUpdater::FindMissingDsts(make({1, 2, 3, 4}), {1, 4, 6, 9})); + UNIT_ASSERT_VALUES_EQUAL(NTvmApi::TClientSettings::TDstVector(), + TNotInitedUpdater::FindMissingDsts(make({1, 2, 3, 4, 5, 6, 7, 8, 9}), {1, 4, 6, 9})); + UNIT_ASSERT_VALUES_EQUAL(NTvmApi::TClientSettings::TDstVector({1, 4, 6, 9}), + TNotInitedUpdater::FindMissingDsts(make({}), {1, 4, 6, 9})); + UNIT_ASSERT_VALUES_EQUAL(NTvmApi::TClientSettings::TDstVector(1, 19), + TNotInitedUpdater::FindMissingDsts(make({213}), {19, 213})); + } + + Y_UNIT_TEST(CreateJsonArray) { + UNIT_ASSERT_VALUES_EQUAL("[]", TNotInitedUpdater::CreateJsonArray({})); + UNIT_ASSERT_VALUES_EQUAL("[sdlzkjvbsdljhfbsdajlhfbsakjdfb]", + TNotInitedUpdater::CreateJsonArray({"sdlzkjvbsdljhfbsdajlhfbsakjdfb"})); + UNIT_ASSERT_VALUES_EQUAL("[sdlzkjvbsdljhfbsdajlhfbsakjdfb,o92q83yh2uhq2eri23r]", + TNotInitedUpdater::CreateJsonArray({"sdlzkjvbsdljhfbsdajlhfbsakjdfb", + "o92q83yh2uhq2eri23r"})); + } + + Y_UNIT_TEST(AppendArrayToJson) { + UNIT_ASSERT_EXCEPTION_CONTAINS(TNotInitedUpdater::AppendToJsonArray("", {}), + yexception, + "previous body required"); + UNIT_ASSERT_EXCEPTION_CONTAINS(TNotInitedUpdater::AppendToJsonArray("[kek", {}), + yexception, + "array is broken:"); + + UNIT_ASSERT_VALUES_EQUAL("[kek]", TNotInitedUpdater::AppendToJsonArray("kek", {})); + + UNIT_ASSERT_VALUES_EQUAL( + "[kek,sdlzkjvbsdljhfbsdajlhfbsakjdfb]", + TNotInitedUpdater::AppendToJsonArray("kek", + {"sdlzkjvbsdljhfbsdajlhfbsakjdfb"})); + UNIT_ASSERT_VALUES_EQUAL( + "[kek,sdlzkjvbsdljhfbsdajlhfbsakjdfb,o92q83yh2uhq2eri23r]", + TNotInitedUpdater::AppendToJsonArray("kek", + {"sdlzkjvbsdljhfbsdajlhfbsakjdfb", "o92q83yh2uhq2eri23r"})); + + UNIT_ASSERT_VALUES_EQUAL( + "[kek,sdlzkjvbsdljhfbsdajlhfbsakjdfb]", + TNotInitedUpdater::AppendToJsonArray("[kek]", + {"sdlzkjvbsdljhfbsdajlhfbsakjdfb"})); + UNIT_ASSERT_VALUES_EQUAL( + "[kek,sdlzkjvbsdljhfbsdajlhfbsakjdfb,o92q83yh2uhq2eri23r]", + TNotInitedUpdater::AppendToJsonArray("[kek]", + {"sdlzkjvbsdljhfbsdajlhfbsakjdfb", "o92q83yh2uhq2eri23r"})); + } + + Y_UNIT_TEST(UpdaterTimeouts) { + const auto timeout = TDuration::MilliSeconds(10); + NTvmApi::TClientSettings s{ + .SelfTvmId = 100500, + .CheckServiceTickets = true, + .TvmHost = "localhost", + .TvmPort = GetRandomPort(), + .TvmSocketTimeout = timeout, + .TvmConnectTimeout = timeout, + }; + + { + auto l = MakeIntrusive<TLogger>(); + auto startTs = ::Now(); + UNIT_ASSERT_EXCEPTION(NTvmApi::TThreadedUpdater::Create(s, l), yexception); + UNIT_ASSERT_LT(::Now() - startTs, timeout * 2); + } + } +} + +template <> +void Out<TSmallVec<TString>>(IOutputStream& out, const TSmallVec<TString>& m) { + for (const TString& s : m) { + out << s << ";"; + } +} + +template <> +void Out<TServiceTickets::TMapIdStr>( + IOutputStream& out, + const TServiceTickets::TMapIdStr& m) { + for (const auto& pair : m) { + out << pair.first << " -> " << pair.second << ";"; + } +} + +template <> +void Out<NTestSuiteApiUpdater::TNotInitedUpdater::TPairTicketsErrors>( + IOutputStream& out, + const NTestSuiteApiUpdater::TNotInitedUpdater::TPairTicketsErrors& m) { + out << m.Tickets << "\n"; + out << m.Errors << "\n"; +} + +template <> +void Out<NTvmAuth::NTvmApi::TClientSettings::TDst>(IOutputStream& out, const NTvmAuth::NTvmApi::TClientSettings::TDst& m) { + out << m.Id; +} diff --git a/library/cpp/tvmauth/client/ut/tvmtool_updater_ut.cpp b/library/cpp/tvmauth/client/ut/tvmtool_updater_ut.cpp new file mode 100644 index 0000000000..9435b46b38 --- /dev/null +++ b/library/cpp/tvmauth/client/ut/tvmtool_updater_ut.cpp @@ -0,0 +1,756 @@ +#include "common.h" + +#include <library/cpp/tvmauth/client/facade.h> +#include <library/cpp/tvmauth/client/misc/tool/threaded_updater.h> + +#include <library/cpp/http/simple/http_client.h> +#include <library/cpp/testing/unittest/registar.h> + +#include <util/system/env.h> + +using namespace NTvmAuth; +using namespace NTvmAuth::NTvmTool; + +Y_UNIT_TEST_SUITE(ToolUpdater) { + static const TString SRV_TICKET = "3:serv:CBAQ__________9_IgYIexCUkQY:GioCM49Ob6_f80y6FY0XBVN4hLXuMlFeyMvIMiDuQnZkbkLpRpQOuQo5YjWoBjM0Vf-XqOm8B7xtrvxSYHDD7Q4OatN2l-Iwg7i71lE3scUeD36x47st3nd0OThvtjrFx_D8mw_c0GT5KcniZlqq1SjhLyAk1b_zJsx8viRAhCU"; + static const TString SRV_TICKET_DST_100503 = "3:serv:CBAQ__________9_IggIwMQHEJeRBg:Kj7VApP6D91UJ8pKpeaE3vYaNTBBJcdYpJLbF9w2-Mb-75s_SmMKkPqqA2rMS358uFfoYpv9YZxq0tIaUj5HPQ1WaQ1yiVuPZ_oi3pJRdr006eRyihM8PUfl6m9ioCFftfOcAg9oN5BGeHTNhn7VWuj3yMg7feaMB0zAUpyaPG0"; + static const TString TEST_TICKET = "3:user:CA0Q__________9_Gg4KAgh7EHsg0oXYzAQoAQ:FSADps3wNGm92Vyb1E9IVq5M6ZygdGdt1vafWWEhfDDeCLoVA-sJesxMl2pGW4OxJ8J1r_MfpG3ZoBk8rLVMHUFrPa6HheTbeXFAWl8quEniauXvKQe4VyrpA1SPgtRoFqi5upSDIJzEAe1YRJjq1EClQ_slMt8R0kA_JjKUX54"; + static const TString PROD_YATEAM_TICKET = "3:user:CAwQ__________9_Gg4KAgh7EHsg0oXYzAQoAg:G2wloFRSi8--RLb2GDSro_sKXPF2JSdL5CVOuOHgUcRvLm-3OxIPn0NUqbJ9DWDmhPplOqEiblIbLK85My1VMJ2aG5SLbRNKEtwfmxLvkwNpl_gUEwWPJm9_8Khslfj71P3hccxtEEqM9bJSMwHueVAY-a9HSzFo-uMFMeSgQ-k"; + + class TMetaInfoProxy: public TMetaInfo { + public: + using TMetaInfo::ApplySettings; + using TMetaInfo::BbEnvFromString; + using TMetaInfo::Config_; + using TMetaInfo::Fetch; + using TMetaInfo::ParseMetaString; + using TMetaInfo::TMetaInfo; + }; + + Y_UNIT_TEST(Settings) { + NTvmTool::TClientSettings s("foo"); + UNIT_ASSERT_EXCEPTION_CONTAINS(s.SetAuthToken("\n "), + TBrokenTvmClientSettings, + "Auth token cannot be empty"); + UNIT_ASSERT_EXCEPTION_CONTAINS(s.GetAuthToken(), + TBrokenTvmClientSettings, + "Auth token cannot be empty. Env 'TVMTOOL_LOCAL_AUTHTOKEN' and 'QLOUD_TVM_TOKEN' are empty."); + + UNIT_ASSERT_NO_EXCEPTION(s.SetAuthToken(AUTH_TOKEN + "\n")); + UNIT_ASSERT_VALUES_EQUAL(AUTH_TOKEN, s.GetAuthToken()); + + UNIT_ASSERT_VALUES_EQUAL("localhost", s.GetHostname()); + UNIT_ASSERT_EXCEPTION_CONTAINS(s.SetHostname(""), + TBrokenTvmClientSettings, + "Hostname cannot be empty"); + + UNIT_ASSERT_NO_EXCEPTION(s.SetHostname("qwe")); + UNIT_ASSERT_VALUES_EQUAL("qwe", s.GetHostname()); + } + + Y_UNIT_TEST(SettingsCtor) { + UNIT_ASSERT_EXCEPTION_CONTAINS(NTvmTool::TClientSettings(""), + TBrokenTvmClientSettings, + "Alias for your TVM client cannot be empty"); + { + NTvmTool::TClientSettings s("self"); + UNIT_ASSERT_EXCEPTION_CONTAINS(s.GetAuthToken(), + TBrokenTvmClientSettings, + "Auth token cannot be empty. " + "Env 'TVMTOOL_LOCAL_AUTHTOKEN' and 'QLOUD_TVM_TOKEN' are empty."); + } + + struct TEnvs { + TEnvs(const std::map<TString, TString>& Env) { + for (const auto& [key, value] : Env) { + Prev[key] = GetEnv(key); + SetEnv(key, value); + } + } + + ~TEnvs() { + for (const auto& [key, value] : Prev) { + SetEnv(key, value); + } + } + + std::map<TString, TString> Prev; + }; + + struct TCase { + std::map<TString, TString> Env; + TString AuthToken; + ui16 Port = 0; + }; + + std::vector<TCase> cases = { + { + { + {"TVMTOOL_LOCAL_AUTHTOKEN", "qwerty"}, + }, + "qwerty", + 1, + }, + { + { + {"TVMTOOL_LOCAL_AUTHTOKEN", "qwerty"}, + {"QLOUD_TVM_TOKEN", "zxcvbn"}, + }, + "qwerty", + 1, + }, + { + { + {"QLOUD_TVM_TOKEN", "zxcvbn"}, + }, + "zxcvbn", + 1, + }, + { + { + {"TVMTOOL_LOCAL_AUTHTOKEN", "qwerty"}, + {"DEPLOY_TVM_TOOL_URL", "32272"}, + }, + "qwerty", + 1, + }, + { + { + {"TVMTOOL_LOCAL_AUTHTOKEN", "qwerty"}, + {"DEPLOY_TVM_TOOL_URL", "localhost:32272"}, + }, + "qwerty", + 32272, + }, + { + { + {"TVMTOOL_LOCAL_AUTHTOKEN", "qwerty"}, + {"DEPLOY_TVM_TOOL_URL", "http://localhost:32272"}, + }, + "qwerty", + 32272, + }, + }; + + for (const TCase& c : cases) { + TEnvs envs(c.Env); + + NTvmTool::TClientSettings s("self"); + UNIT_ASSERT_VALUES_EQUAL(c.AuthToken, s.GetAuthToken()); + UNIT_ASSERT_VALUES_EQUAL(c.Port, s.GetPort()); + } + } + + Y_UNIT_TEST(Meta_Fetch) { + TPortManager pm; + ui16 port = pm.GetPort(80); + NMock::TMockServer server(port, []() { return new TTvmTool; }); + TKeepAliveHttpClient client("localhost", port); + + TMetaInfoProxy m(nullptr); + NTvmTool::TClientSettings settings("me"); + settings.SetAuthToken(AUTH_TOKEN); + m.ApplySettings(settings); + + UNIT_ASSERT_VALUES_EQUAL(META, m.Fetch(client)); + + settings.SetAuthToken("qwerty"); + m.ApplySettings(settings); + UNIT_ASSERT_EXCEPTION_CONTAINS(m.Fetch(client), + TNonRetriableException, + "Failed to fetch meta from tvmtool: localhost:"); + + settings.SetAuthToken(AUTH_TOKEN); + m.ApplySettings(settings); + { + TKeepAliveHttpClient client("localhost", 0); + UNIT_ASSERT_EXCEPTION_CONTAINS(m.Fetch(client), + TRetriableException, + "Failed to fetch meta data from tvmtool: "); + } + + server.SetGenerator([]() { + auto p = new TTvmTool; + p->Code = HTTP_NOT_FOUND; + return p; }); + UNIT_ASSERT_EXCEPTION_CONTAINS(m.Fetch(client), + TNonRetriableException, + "Library does not support so old tvmtool. You need tvmtool>=1.1.0"); + server.SetGenerator([]() { + auto p = new TTvmTool; + p->Code = HTTP_INTERNAL_SERVER_ERROR; + return p; }); + UNIT_ASSERT_EXCEPTION_CONTAINS(m.Fetch(client), + TRetriableException, + "Failed to fetch meta from tvmtool: localhost:"); + } + + Y_UNIT_TEST(Meta_ParseMetaString_me) { + TMetaInfo::TConfigPtr c; + UNIT_ASSERT(c = TMetaInfoProxy::ParseMetaString(META, "me")); + UNIT_ASSERT_VALUES_EQUAL(100500, c->SelfTvmId); + UNIT_ASSERT_EQUAL(EBlackboxEnv::ProdYateam, c->BbEnv); + UNIT_ASSERT_VALUES_EQUAL("", c->IdmSlug); + UNIT_ASSERT_EQUAL(TMetaInfo::TDstAliases({{"bbox", 242}, {"pass_likers", 11}}), c->DstAliases); + } + + Y_UNIT_TEST(Meta_ParseMetaString_tenant_with_roles) { + TMetaInfo::TConfigPtr c; + UNIT_ASSERT(c = TMetaInfoProxy::ParseMetaString(META, "tenant_with_roles")); + UNIT_ASSERT_VALUES_EQUAL(100500, c->SelfTvmId); + UNIT_ASSERT_VALUES_EQUAL(EBlackboxEnv::ProdYateam, c->BbEnv); + UNIT_ASSERT_VALUES_EQUAL("some_slug", c->IdmSlug); + UNIT_ASSERT_VALUES_EQUAL(TMetaInfo::TDstAliases(), c->DstAliases); + } + + Y_UNIT_TEST(Meta_ParseMetaString_pc) { + TMetaInfo::TConfigPtr c; + UNIT_ASSERT(c = TMetaInfoProxy::ParseMetaString(META, "push-client")); + UNIT_ASSERT_VALUES_EQUAL(100501, c->SelfTvmId); + UNIT_ASSERT_EQUAL(EBlackboxEnv::ProdYateam, c->BbEnv); + UNIT_ASSERT_VALUES_EQUAL("", c->IdmSlug); + UNIT_ASSERT_EQUAL(TMetaInfo::TDstAliases({{"pass_likers", 100502}}), c->DstAliases); + } + + Y_UNIT_TEST(Meta_ParseMetaString_se) { + TMetaInfo::TConfigPtr c; + UNIT_ASSERT(c = TMetaInfoProxy::ParseMetaString(META, "something_else")); + UNIT_ASSERT_VALUES_EQUAL(100503, c->SelfTvmId); + UNIT_ASSERT_EQUAL(EBlackboxEnv::ProdYateam, c->BbEnv); + UNIT_ASSERT_VALUES_EQUAL("", c->IdmSlug); + UNIT_ASSERT(c->DstAliases.empty()); + } + + Y_UNIT_TEST(Meta_ParseMetaString_errors) { + TMetaInfoProxy m(nullptr); + UNIT_ASSERT(!m.ParseMetaString(META, "ololo")); + + TString meta = "}"; + UNIT_ASSERT_EXCEPTION_CONTAINS(m.ParseMetaString(meta, "qqq"), yexception, meta); + meta = "{}"; + UNIT_ASSERT_EXCEPTION_CONTAINS(m.ParseMetaString(meta, "qqq"), yexception, meta); + meta = R"({"tenants" : {}})"; + UNIT_ASSERT_EXCEPTION_CONTAINS(m.ParseMetaString(meta, "qqq"), yexception, meta); + meta = R"({"tenants" : [{"self":{}}]})"; + UNIT_ASSERT_EXCEPTION_CONTAINS(m.ParseMetaString(meta, "qqq"), yexception, meta); + } + + Y_UNIT_TEST(Meta_BbEnvFromString) { + UNIT_ASSERT_VALUES_EQUAL(EBlackboxEnv::Prod, TMetaInfoProxy::BbEnvFromString("Prod", META)); + UNIT_ASSERT_VALUES_EQUAL(EBlackboxEnv::Test, TMetaInfoProxy::BbEnvFromString("Test", META)); + UNIT_ASSERT_VALUES_EQUAL(EBlackboxEnv::ProdYateam, TMetaInfoProxy::BbEnvFromString("ProdYaTeam", META)); + UNIT_ASSERT_VALUES_EQUAL(EBlackboxEnv::TestYateam, TMetaInfoProxy::BbEnvFromString("TestYaTeam", META)); + UNIT_ASSERT_VALUES_EQUAL(EBlackboxEnv::Stress, TMetaInfoProxy::BbEnvFromString("Stress", META)); + UNIT_ASSERT_EXCEPTION_CONTAINS(TMetaInfoProxy::BbEnvFromString("foo", META), + yexception, + "'bb_env'=='foo'"); + } + + Y_UNIT_TEST(Meta_ApplySettings) { + NTvmTool::TClientSettings s("foo"); + s.SetAuthToken(AUTH_TOKEN); + + TMetaInfoProxy m(nullptr); + m.ApplySettings(s); + + UNIT_ASSERT_VALUES_EQUAL( + TKeepAliveHttpClient::THeaders({{"Authorization", AUTH_TOKEN}}), + m.GetAuthHeader()); + } + + Y_UNIT_TEST(Meta_Init) { + TPortManager pm; + ui16 port = pm.GetPort(80); + NMock::TMockServer server(port, []() { return new TTvmTool; }); + TKeepAliveHttpClient client("localhost", port); + + NTvmTool::TClientSettings s("me"); + s.SetAuthToken(AUTH_TOKEN); + s.SetPort(port); + auto l = MakeIntrusive<TLogger>(); + TMetaInfo m(l); + UNIT_ASSERT_NO_EXCEPTION(m.Init(client, s)); + UNIT_ASSERT_VALUES_EQUAL(100500, m.GetConfig()->SelfTvmId); + UNIT_ASSERT_EQUAL(EBlackboxEnv::ProdYateam, m.GetConfig()->BbEnv); + UNIT_ASSERT_EQUAL(TMetaInfo::TDstAliases({{"bbox", 242}, {"pass_likers", 11}}), m.GetConfig()->DstAliases); + UNIT_ASSERT_VALUES_EQUAL(TStringBuilder() + << "7: Meta info fetched from localhost:" << port << "\n" + << "6: Meta: self_tvm_id=100500, bb_env=ProdYateam, idm_slug=<NULL>, dsts=[(pass_likers:11)(bbox:242)]\n", + l->Stream.Str()); + l->Stream.Clear(); + UNIT_ASSERT_VALUES_EQUAL( + "/tvm/tickets?src=100500&dsts=11,242", + TMetaInfo::GetRequestForTickets(*m.GetConfig())); + + server.SetGenerator([]() { + auto p = new TTvmTool; + p->Meta = R"({ + "bb_env" : "Prod", + "tenants" : [{ + "self": {"alias" : "me", "client_id": 100500}, + "dsts" : [{"alias" : "pass_likers","client_id": 11}] + }] + })"; + return p; }); + UNIT_ASSERT(m.TryUpdateConfig(client)); + UNIT_ASSERT_VALUES_EQUAL( + "6: Meta was updated. Old: (self_tvm_id=100500, bb_env=ProdYateam, idm_slug=<NULL>, dsts=[(pass_likers:11)(bbox:242)]). New: (self_tvm_id=100500, bb_env=Prod, idm_slug=<NULL>, dsts=[(pass_likers:11)])\n", + l->Stream.Str()); + l->Stream.clear(); + + s = NTvmTool::TClientSettings("foo"); + s.SetAuthToken(AUTH_TOKEN); + s.SetPort(port); + TMetaInfo m2(l); + UNIT_ASSERT_EXCEPTION_CONTAINS(m2.Init(client, s), TNonRetriableException, "Alias 'foo' not found in meta info"); + UNIT_ASSERT_VALUES_EQUAL(TStringBuilder() + << "7: Meta info fetched from localhost:" << port << "\n", + l->Stream.Str()); + UNIT_ASSERT_EXCEPTION_CONTAINS(TMetaInfo::GetRequestForTickets({}), + yexception, + "DstAliases.empty()"); + + server.SetGenerator([]() { + auto p = new TTvmTool; + p->Meta = "}"; + return p; }); + UNIT_ASSERT_EXCEPTION_CONTAINS(m.Init(client, s), + TNonRetriableException, + "Malformed json from tvmtool:"); + } + + class TNonInitedUpdater: public TThreadedUpdater { + public: + TNonInitedUpdater(const TString& host, ui16 port, TLoggerPtr logger) + : TThreadedUpdater(host, port, TDuration::Seconds(5), TDuration::Seconds(30), logger) + { + } + + using TThreadedUpdater::ArePublicKeysOk; + using TThreadedUpdater::AreServiceTicketsOk; + using TThreadedUpdater::FetchPublicKeys; + using TThreadedUpdater::FetchServiceTickets; + using TThreadedUpdater::GetBirthTimeFromResponse; + using TThreadedUpdater::Init; + using TThreadedUpdater::IsTimeToUpdatePublicKeys; + using TThreadedUpdater::IsTimeToUpdateServiceTickets; + using TThreadedUpdater::LastVisitForConfig_; + using TThreadedUpdater::MetaInfo_; + using TThreadedUpdater::ParseFetchTicketsResponse; + using TThreadedUpdater::SetBbEnv; + using TThreadedUpdater::SetServiceContext; + using TThreadedUpdater::SetServiceTickets; + using TThreadedUpdater::SetUpdateTimeOfPublicKeys; + using TThreadedUpdater::SetUpdateTimeOfServiceTickets; + using TThreadedUpdater::SetUserContext; + using TThreadedUpdater::TPairTicketsErrors; + using TThreadedUpdater::UpdateKeys; + using TThreadedUpdater::UpdateServiceTickets; + }; + + Y_UNIT_TEST(GetBirthTimeFromResponse) { + THttpHeaders h; + UNIT_ASSERT_EXCEPTION_CONTAINS(TNonInitedUpdater::GetBirthTimeFromResponse(h, "ololo"), + yexception, + "Failed to fetch bithtime of ololo from tvmtool"); + + h.AddHeader(THttpInputHeader("X-Ya-Tvmtool-Data-Birthtime: qwe")); + UNIT_ASSERT_EXCEPTION_CONTAINS(TNonInitedUpdater::GetBirthTimeFromResponse(h, "ololo"), + yexception, + "Bithtime of ololo from tvmtool must be unixtime. Got: qwe"); + + h.AddOrReplaceHeader(THttpInputHeader("X-Ya-Tvmtool-Data-Birthtime: 123")); + UNIT_ASSERT_VALUES_EQUAL(TInstant::Seconds(123), TNonInitedUpdater::GetBirthTimeFromResponse(h, "ololo")); + } + + Y_UNIT_TEST(Fetch) { + TPortManager pm; + ui16 port = pm.GetPort(80); + NMock::TMockServer server(port, []() { return new TTvmTool; }); + TKeepAliveHttpClient client("localhost", port); + + auto l = MakeIntrusive<TLogger>(); + TNonInitedUpdater u("localhost", port, l); + NTvmTool::TClientSettings s("me"); + s.SetAuthToken(AUTH_TOKEN); + s.SetPort(port); + u.MetaInfo_.Init(client, s); + auto p = u.FetchPublicKeys(); + UNIT_ASSERT_STRINGS_EQUAL(NUnittest::TVMKNIFE_PUBLIC_KEYS, p.first); + UNIT_ASSERT_VALUES_EQUAL(BIRTHTIME, p.second); + + auto p2 = u.FetchServiceTickets(*u.MetaInfo_.GetConfig()); + UNIT_ASSERT_STRINGS_EQUAL(TICKETS_ME, p2.first); + UNIT_ASSERT_VALUES_EQUAL(BIRTHTIME, p2.second); + + UNIT_ASSERT_VALUES_EQUAL( + TStringBuilder() + << "7: Meta info fetched from localhost:" << port << "\n" + << "6: Meta: self_tvm_id=100500, bb_env=ProdYateam, idm_slug=<NULL>, dsts=[(pass_likers:11)(bbox:242)]\n", + l->Stream.Str()); + } + + Y_UNIT_TEST(ParseFetchTicketsResponse) { + auto l = MakeIntrusive<TLogger>(); + TNonInitedUpdater u("", 0, l); + + UNIT_ASSERT_EXCEPTION_CONTAINS(u.ParseFetchTicketsResponse("}", {}), + yexception, + "Invalid json from tvmtool: }"); + + auto t = u.ParseFetchTicketsResponse(TICKETS_ME, {{"pass_likers", 11}, {"se", 2}}); + auto expected = TNonInitedUpdater::TPairTicketsErrors{ + {{11, "3:serv:CBAQ__________9_IgYIlJEGEAs:T-apeMNWFc_vHPQ3iLaZv9NjG-hf5-i23O4AhRu1M68ryN3FU5qvyqTSSiPbtJdFP6EE41QQBzEs59dHn9DRkqQNwwKf1is00Oewwj2XKO0uHukuzd9XxZnro7MfjPswsjWufxX28rmJtlfSXwAtyKt8TI5yKJnMeBPQ0m5R3k8"}}, + { + {2, "Missing tvm_id in response, should never happend: se"}, + }, + }; + UNIT_ASSERT_VALUES_EQUAL(expected, t); + + UNIT_ASSERT_VALUES_EQUAL( + TStringBuilder() + << "3: Failed to get ServiceTicket for se (2): Missing tvm_id in response, should never happend: se\n", + l->Stream.Str()); + } + + Y_UNIT_TEST(Update) { + TPortManager pm; + ui16 port = pm.GetPort(80); + NMock::TMockServer server(port, []() { return new TTvmTool; }); + TKeepAliveHttpClient client("localhost", port); + + auto l = MakeIntrusive<TLogger>(); + TNonInitedUpdater u("localhost", port, l); + NTvmTool::TClientSettings s("me"); + s.SetAuthToken(AUTH_TOKEN); + s.SetPort(port); + u.MetaInfo_.Init(client, s); + + using TTickets = TServiceTickets::TMapAliasStr; + UNIT_ASSERT(!u.GetCachedServiceTickets()); + UNIT_ASSERT_VALUES_EQUAL(BIRTHTIME, u.UpdateServiceTickets(*u.MetaInfo_.GetConfig())); + UNIT_ASSERT(u.GetCachedServiceTickets()); + UNIT_ASSERT_VALUES_EQUAL(TInstant(), u.GetUpdateTimeOfServiceTickets()); + UNIT_ASSERT_EQUAL( + TTickets({ + {"bbox", "3:serv:CBAQ__________9_IgcIlJEGEPIB:N7luw0_rVmBosTTI130jwDbQd0-cMmqJeEl0ma4ZlIo_mHXjBzpOuMQ3A9YagbmOBOt8TZ_gzGvVSegWZkEeB24gM22acw0w-RcHaQKrzSOA5Zq8WLNIC8QUa4_WGTlAsb7R7eC4KTAGgouIquNAgMBdTuGOuZHnMLvZyLnOMKc"}, + {"pass_likers", "3:serv:CBAQ__________9_IgYIlJEGEAs:T-apeMNWFc_vHPQ3iLaZv9NjG-hf5-i23O4AhRu1M68ryN3FU5qvyqTSSiPbtJdFP6EE41QQBzEs59dHn9DRkqQNwwKf1is00Oewwj2XKO0uHukuzd9XxZnro7MfjPswsjWufxX28rmJtlfSXwAtyKt8TI5yKJnMeBPQ0m5R3k8"}, + }), + u.GetCachedServiceTickets()->TicketsByAlias); + UNIT_ASSERT_VALUES_EQUAL( + TStringBuilder() + << "7: Meta info fetched from localhost:" << port << "\n" + << "6: Meta: self_tvm_id=100500, bb_env=ProdYateam, idm_slug=<NULL>, dsts=[(pass_likers:11)(bbox:242)]\n", + l->Stream.Str()); + l->Stream.Clear(); + + UNIT_ASSERT(!u.GetCachedServiceContext()); + UNIT_ASSERT(!u.GetCachedUserContext()); + UNIT_ASSERT_VALUES_EQUAL(BIRTHTIME, u.UpdateKeys(*u.MetaInfo_.GetConfig())); + UNIT_ASSERT(u.GetCachedServiceContext()); + UNIT_ASSERT(!u.GetCachedUserContext()); + u.SetBbEnv(EBlackboxEnv::Test); + UNIT_ASSERT(u.GetCachedUserContext()); + UNIT_ASSERT_VALUES_EQUAL("", l->Stream.Str()); + l->Stream.Clear(); + + { + TAsyncUpdaterPtr u = TThreadedUpdater::Create(s, l); + UNIT_ASSERT(u->GetCachedServiceTickets()); + UNIT_ASSERT(u->GetCachedServiceContext()); + UNIT_ASSERT(u->GetCachedUserContext()); + UNIT_ASSERT_VALUES_EQUAL(TClientStatus::Ok, u->GetStatus()); + + NTvmAuth::TTvmClient c(u); + UNIT_ASSERT(c.CheckServiceTicket(SRV_TICKET)); + UNIT_ASSERT(!c.CheckServiceTicket(SRV_TICKET_DST_100503)); + UNIT_ASSERT(c.CheckUserTicket(PROD_YATEAM_TICKET)); + UNIT_ASSERT_VALUES_EQUAL("3:serv:CBAQ__________9_IgYIlJEGEAs:T-apeMNWFc_vHPQ3iLaZv9NjG-hf5-i23O4AhRu1M68ryN3FU5qvyqTSSiPbtJdFP6EE41QQBzEs59dHn9DRkqQNwwKf1is00Oewwj2XKO0uHukuzd9XxZnro7MfjPswsjWufxX28rmJtlfSXwAtyKt8TI5yKJnMeBPQ0m5R3k8", c.GetServiceTicketFor("pass_likers")); + } + UNIT_ASSERT_VALUES_EQUAL( + TStringBuilder() + << "7: Meta info fetched from localhost:" << port << "\n" + << "6: Meta: self_tvm_id=100500, bb_env=ProdYateam, idm_slug=<NULL>, dsts=[(pass_likers:11)(bbox:242)]\n" + << "7: Tickets fetched from tvmtool: 2425-09-17T11:04:00.000000Z\n" + << "7: Public keys fetched from tvmtool: 2425-09-17T11:04:00.000000Z\n" + << "7: Thread-worker started\n" + << "7: Thread-worker stopped\n", + l->Stream.Str()); + l->Stream.Clear(); + + { + NTvmTool::TClientSettings s("something_else"); + s.SetAuthToken(AUTH_TOKEN); + s.SetPort(port); + + TAsyncUpdaterPtr u = TThreadedUpdater::Create(s, l); + UNIT_ASSERT(!u->GetCachedServiceTickets()); + UNIT_ASSERT(u->GetCachedServiceContext()); + UNIT_ASSERT(u->GetCachedUserContext()); + UNIT_ASSERT_VALUES_EQUAL(TClientStatus::Ok, u->GetStatus()); + + NTvmAuth::TTvmClient c(u); + UNIT_ASSERT(!c.CheckServiceTicket(SRV_TICKET)); + UNIT_ASSERT(c.CheckServiceTicket(SRV_TICKET_DST_100503)); + UNIT_ASSERT(c.CheckUserTicket(PROD_YATEAM_TICKET)); + UNIT_ASSERT_EXCEPTION_CONTAINS(c.GetServiceTicketFor("pass_likers"), + TBrokenTvmClientSettings, + "Need to enable ServiceTickets fetching"); + } + UNIT_ASSERT_VALUES_EQUAL( + TStringBuilder() + << "7: Meta info fetched from localhost:" << port << "\n" + << "6: Meta: self_tvm_id=100503, bb_env=ProdYateam, idm_slug=<NULL>, dsts=[]\n" + << "7: Public keys fetched from tvmtool: 2425-09-17T11:04:00.000000Z\n" + << "7: Thread-worker started\n" + << "7: Thread-worker stopped\n", + l->Stream.Str()); + l->Stream.Clear(); + } + + Y_UNIT_TEST(IsOk) { + TNonInitedUpdater u("", 0, TDevNullLogger::IAmBrave()); + using TTickets = TServiceTickets::TMapIdStr; + + UNIT_ASSERT(u.AreServiceTicketsOk(0)); + UNIT_ASSERT(!u.AreServiceTicketsOk(2)); + u.SetServiceTickets(MakeIntrusiveConst<TServiceTickets>(TTickets(), + TTickets(), + TServiceTickets::TMapAliasId())); + UNIT_ASSERT(u.AreServiceTicketsOk(0)); + UNIT_ASSERT(!u.AreServiceTicketsOk(2)); + u.SetServiceTickets(MakeIntrusiveConst<TServiceTickets>( + TTickets({ + {1, "mega_ticket"}, + {2, "mega_ticket2"}, + }), + TTickets({ + {3, "mega_error3"}, + }), + TServiceTickets::TMapAliasId())); + UNIT_ASSERT(u.AreServiceTicketsOk(0)); + UNIT_ASSERT(!u.AreServiceTicketsOk(2)); + + u.SetServiceTickets(MakeIntrusiveConst<TServiceTickets>( + TTickets({ + {1, "mega_ticket"}, + {2, "mega_ticket2"}, + }), + TTickets({ + {3, "mega_error3"}, + }), + TServiceTickets::TMapAliasId({ + {"mega_ticket", 1}, + {"mega_ticket2", 2}, + {"mega_ticket3", 3}, + }))); + UNIT_ASSERT(u.AreServiceTicketsOk(2)); + + UNIT_ASSERT(!u.ArePublicKeysOk()); + u.SetServiceContext(MakeIntrusiveConst<TServiceContext>( + TServiceContext::CheckingFactory(12, NUnittest::TVMKNIFE_PUBLIC_KEYS))); + UNIT_ASSERT(!u.ArePublicKeysOk()); + u.SetUserContext(NUnittest::TVMKNIFE_PUBLIC_KEYS); + UNIT_ASSERT(!u.ArePublicKeysOk()); + u.SetBbEnv(EBlackboxEnv::Test); + UNIT_ASSERT(u.ArePublicKeysOk()); + } + + Y_UNIT_TEST(IsTimeToUpdate) { + TNonInitedUpdater u("", 0, TDevNullLogger::IAmBrave()); + + UNIT_ASSERT(!u.IsTimeToUpdatePublicKeys(TInstant::Now() - TDuration::Seconds(597))); + UNIT_ASSERT(u.IsTimeToUpdatePublicKeys(TInstant::Now() - TDuration::Seconds(603))); + + TMetaInfo::TConfig cfg; + UNIT_ASSERT(!u.IsTimeToUpdateServiceTickets(cfg, TInstant::Now() - TDuration::Seconds(597))); + UNIT_ASSERT(!u.IsTimeToUpdateServiceTickets(cfg, TInstant::Now() - TDuration::Seconds(603))); + + cfg.DstAliases = {{"q", 1}}; + UNIT_ASSERT(!u.IsTimeToUpdateServiceTickets(cfg, TInstant::Now() - TDuration::Seconds(597))); + UNIT_ASSERT(u.IsTimeToUpdateServiceTickets(cfg, TInstant::Now() - TDuration::Seconds(603))); + } + + Y_UNIT_TEST(InitWithOldData) { + TPortManager pm; + ui16 port = pm.GetPort(80); + NMock::TMockServer server(port, + []() { + auto p = new TTvmTool; + p->Birthtime = TInstant::Seconds(123); + return p; + }); + + NTvmTool::TClientSettings s("me"); + s.SetAuthToken(AUTH_TOKEN); + s.SetPort(port); + + auto l = MakeIntrusive<TLogger>(); + UNIT_ASSERT_EXCEPTION_CONTAINS(TThreadedUpdater::Create(s, l), + TRetriableException, + "Failed to start TvmClient. You can retry: "); + UNIT_ASSERT_VALUES_EQUAL( + TStringBuilder() + << "7: Meta info fetched from localhost:" << port << "\n" + << "6: Meta: self_tvm_id=100500, bb_env=ProdYateam, idm_slug=<NULL>, dsts=[(pass_likers:11)(bbox:242)]\n" + << "4: Error while fetching of tickets: Service tickets are too old: 1970-01-01T00:02:03.000000Z\n" + << "3: Service tickets have not been refreshed for too long period\n" + << "4: Error while fetching of public keys: Public keys are too old: 1970-01-01T00:02:03.000000Z\n" + << "3: Public keys have not been refreshed for too long period\n" + << "4: Error while fetching of tickets: Service tickets are too old: 1970-01-01T00:02:03.000000Z\n" + << "3: Service tickets have not been refreshed for too long period\n" + << "4: Error while fetching of public keys: Public keys are too old: 1970-01-01T00:02:03.000000Z\n" + << "3: Public keys have not been refreshed for too long period\n" + << "4: Error while fetching of tickets: Service tickets are too old: 1970-01-01T00:02:03.000000Z\n" + << "3: Service tickets have not been refreshed for too long period\n" + << "4: Error while fetching of public keys: Public keys are too old: 1970-01-01T00:02:03.000000Z\n" + << "3: Public keys have not been refreshed for too long period\n", + l->Stream.Str()); + } + + Y_UNIT_TEST(InitWithOldData_onlyKeys) { + TPortManager pm; + ui16 port = pm.GetPort(80); + NMock::TMockServer server(port, + []() { + auto p = new TTvmTool; + p->Birthtime = TInstant::Seconds(123); + return p; + }); + + NTvmTool::TClientSettings s("something_else"); + s.SetAuthToken(AUTH_TOKEN); + s.SetPort(port); + + { + s.OverrideBlackboxEnv(EBlackboxEnv::Stress); + auto l = MakeIntrusive<TLogger>(); + UNIT_ASSERT_EXCEPTION_CONTAINS(TThreadedUpdater::Create(s, l), + TBrokenTvmClientSettings, + "Overriding of BlackboxEnv is illegal: ProdYateam -> Stress"); + } + + s.OverrideBlackboxEnv(EBlackboxEnv::Prod); + auto l = MakeIntrusive<TLogger>(); + UNIT_ASSERT_EXCEPTION_CONTAINS(TThreadedUpdater::Create(s, l), + TRetriableException, + "Failed to start TvmClient. You can retry: "); + UNIT_ASSERT_VALUES_EQUAL( + TStringBuilder() + << "7: Meta info fetched from localhost:" << port << "\n" + << "6: Meta: self_tvm_id=100503, bb_env=ProdYateam, idm_slug=<NULL>, dsts=[]\n" + << "6: Meta: override blackbox env: ProdYateam->Prod\n" + << "4: Error while fetching of public keys: Public keys are too old: 1970-01-01T00:02:03.000000Z\n" + << "3: Public keys have not been refreshed for too long period\n" + << "4: Error while fetching of public keys: Public keys are too old: 1970-01-01T00:02:03.000000Z\n" + << "3: Public keys have not been refreshed for too long period\n" + << "4: Error while fetching of public keys: Public keys are too old: 1970-01-01T00:02:03.000000Z\n" + << "3: Public keys have not been refreshed for too long period\n", + l->Stream.Str()); + } + + Y_UNIT_TEST(Init) { + TPortManager pm; + ui16 port = pm.GetPort(80); + NMock::TMockServer server(port, []() { return new TTvmTool; }); + + NTvmTool::TClientSettings s("push-client"); + s.SetAuthToken(AUTH_TOKEN); + s.SetPort(port); + s.SetHostname("localhost"); + + auto l = MakeIntrusive<TLogger>(); + { + TAsyncUpdaterPtr u = TThreadedUpdater::Create(s, l); + UNIT_ASSERT_VALUES_EQUAL(TClientStatus::Ok, u->GetStatus()); + } + UNIT_ASSERT_VALUES_EQUAL( + TStringBuilder() + << "7: Meta info fetched from localhost:" << port << "\n" + << "6: Meta: self_tvm_id=100501, bb_env=ProdYateam, idm_slug=<NULL>, dsts=[(pass_likers:100502)]\n" + << "7: Tickets fetched from tvmtool: 2425-09-17T11:04:00.000000Z\n" + << "7: Public keys fetched from tvmtool: 2425-09-17T11:04:00.000000Z\n" + << "7: Thread-worker started\n" + << "7: Thread-worker stopped\n", + l->Stream.Str()); + } + + Y_UNIT_TEST(InitWithoutTvmtool) { + NTvmTool::TClientSettings s("me"); + s.SetAuthToken(AUTH_TOKEN); + s.SetPort(0); + + UNIT_ASSERT_EXCEPTION_CONTAINS(TThreadedUpdater::Create(s, TDevNullLogger::IAmBrave()), + TNonRetriableException, + "can not connect to "); + } + + Y_UNIT_TEST(GetStatus) { + TNonInitedUpdater u("", 0, TDevNullLogger::IAmBrave()); + TMetaInfoProxy m(nullptr); + m.Config_ = std::make_shared<TMetaInfo::TConfig>(); + u.MetaInfo_ = m; + u.LastVisitForConfig_ = TInstant::Now(); + + UNIT_ASSERT_VALUES_EQUAL(TClientStatus::Error, u.GetStatus()); + u.SetUpdateTimeOfPublicKeys(TInstant::Now() - TDuration::Days(3)); + UNIT_ASSERT_VALUES_EQUAL(TClientStatus::Warning, u.GetStatus()); + u.SetUpdateTimeOfPublicKeys(TInstant::Now() - TDuration::Hours(3)); + UNIT_ASSERT_VALUES_EQUAL(TClientStatus::Ok, u.GetStatus()); + + u.SetServiceTickets(new TServiceTickets({}, {}, {})); + + TMetaInfo::TConfig cfg; + cfg.DstAliases = {{"q", 1}, {"q2", 2}}; + m.Config_ = std::make_shared<TMetaInfo::TConfig>(cfg); + u.MetaInfo_ = m; + UNIT_ASSERT_VALUES_EQUAL(TClientStatus::Error, u.GetStatus()); + u.SetUpdateTimeOfServiceTickets(TInstant::Now() - TDuration::Hours(3)); + UNIT_ASSERT_VALUES_EQUAL(TClientStatus::Error, u.GetStatus()); + + u.SetServiceTickets(MakeIntrusiveConst<TServiceTickets>( + TServiceTickets::TMapIdStr({{1, "3:serv:CBAQ__________9_IgYIKhCUkQY:CX"}, {2, "t"}}), + TServiceTickets::TMapIdStr({{3, "mega_error"}, {4, "error2"}}), + TServiceTickets::TMapAliasId({ + {"some_alias#1", 1}, + {"some_alias#2", 2}, + }))); + UNIT_ASSERT_VALUES_EQUAL(TClientStatus::Warning, u.GetStatus()); + + const TInstant* inv = &u.GetCachedServiceTickets()->InvalidationTime; + *const_cast<TInstant*>(inv) = TInstant::Now() + TDuration::Hours(3); + UNIT_ASSERT_VALUES_EQUAL(TClientStatus::Warning, u.GetStatus()); + + u.SetUpdateTimeOfServiceTickets(TInstant::Now() - TDuration::Minutes(3)); + UNIT_ASSERT_VALUES_EQUAL(TClientStatus::Ok, u.GetStatus()); + + u.LastVisitForConfig_ = TInstant::Now() - TDuration::Minutes(1); + UNIT_ASSERT_VALUES_EQUAL(TClientStatus::Warning, u.GetStatus()); + } + + Y_UNIT_TEST(multiNamesForDst) { + TPortManager pm; + ui16 port = pm.GetPort(80); + NMock::TMockServer server(port, []() { return new TTvmTool; }); + + NTvmTool::TClientSettings s("multi_names_for_dst"); + s.SetAuthToken(AUTH_TOKEN); + s.SetPort(port); + s.SetHostname("localhost"); + + auto l = MakeIntrusive<TLogger>(); + { + TAsyncUpdaterPtr u = TThreadedUpdater::Create(s, l); + UNIT_ASSERT_VALUES_EQUAL(TClientStatus::Ok, u->GetStatus()); + } + UNIT_ASSERT_VALUES_EQUAL( + TStringBuilder() + << "7: Meta info fetched from localhost:" << port << "\n" + << "6: Meta: self_tvm_id=100599, bb_env=ProdYateam, idm_slug=<NULL>, dsts=[(pass_haters:100502)(pass_likers:100502)]\n" + << "7: Tickets fetched from tvmtool: 2425-09-17T11:04:00.000000Z\n" + << "7: Public keys fetched from tvmtool: 2425-09-17T11:04:00.000000Z\n" + << "7: Thread-worker started\n" + << "7: Thread-worker stopped\n", + l->Stream.Str()); + } +} diff --git a/library/cpp/tvmauth/client/ut/utils_ut.cpp b/library/cpp/tvmauth/client/ut/utils_ut.cpp new file mode 100644 index 0000000000..e780fb2779 --- /dev/null +++ b/library/cpp/tvmauth/client/ut/utils_ut.cpp @@ -0,0 +1,88 @@ +#include <library/cpp/tvmauth/client/misc/utils.h> + +#include <library/cpp/testing/unittest/registar.h> + +Y_UNIT_TEST_SUITE(UtilsTest) { + using namespace NTvmAuth; + + Y_UNIT_TEST(ParseDstMap) { + using TMap = NTvmAuth::NTvmApi::TClientSettings::TDstMap; + UNIT_ASSERT_EQUAL(TMap(), NUtils::ParseDstMap("")); + UNIT_ASSERT_EXCEPTION(NUtils::ParseDstMap(";"), TFromStringException); + UNIT_ASSERT_EXCEPTION(NUtils::ParseDstMap(":"), TFromStringException); + UNIT_ASSERT_EXCEPTION(NUtils::ParseDstMap("3;"), TFromStringException); + UNIT_ASSERT_EXCEPTION(NUtils::ParseDstMap("3:foo;"), TFromStringException); + + UNIT_ASSERT_EQUAL(TMap({ + {"foo", 3}, + }), + NUtils::ParseDstMap("foo:3")); + UNIT_ASSERT_EQUAL(TMap({ + {"foo", 3}, + {"bar", 17}, + }), + NUtils::ParseDstMap("foo:3;bar:17;")); + } + + Y_UNIT_TEST(ParseDstVector) { + using TVector = NTvmAuth::NTvmApi::TClientSettings::TDstVector; + UNIT_ASSERT_EQUAL(TVector(), NUtils::ParseDstVector("")); + UNIT_ASSERT_EXCEPTION_CONTAINS(NUtils::ParseDstVector(";"), + yexception, + "Cannot parse empty string as number"); + UNIT_ASSERT_EXCEPTION_CONTAINS(NUtils::ParseDstVector(":"), + yexception, + "Unexpected symbol"); + UNIT_ASSERT_EXCEPTION_CONTAINS(NUtils::ParseDstVector("3:foo;"), + yexception, + "Unexpected symbol"); + UNIT_ASSERT_EXCEPTION_CONTAINS(NUtils::ParseDstVector("foo:3;"), + yexception, + "Unexpected symbol"); + + UNIT_ASSERT_EQUAL(TVector(1, 3), + NUtils::ParseDstVector("3")); + UNIT_ASSERT_EQUAL(TVector({3, 17}), + NUtils::ParseDstVector("3;17;")); + } + + Y_UNIT_TEST(ToHex) { + UNIT_ASSERT_VALUES_EQUAL("", NUtils::ToHex("")); + UNIT_ASSERT_VALUES_EQUAL("61", NUtils::ToHex("a")); + UNIT_ASSERT_VALUES_EQUAL( + "6C6B787A6E7620736C6A6876627761656220", + NUtils::ToHex("lkxznv sljhvbwaeb ")); + } + + Y_UNIT_TEST(CheckBbEnvOverriding) { + UNIT_ASSERT(NUtils::CheckBbEnvOverriding(EBlackboxEnv::Prod, EBlackboxEnv::Prod)); + UNIT_ASSERT(NUtils::CheckBbEnvOverriding(EBlackboxEnv::Prod, EBlackboxEnv::ProdYateam)); + UNIT_ASSERT(!NUtils::CheckBbEnvOverriding(EBlackboxEnv::Prod, EBlackboxEnv::Test)); + UNIT_ASSERT(!NUtils::CheckBbEnvOverriding(EBlackboxEnv::Prod, EBlackboxEnv::TestYateam)); + UNIT_ASSERT(!NUtils::CheckBbEnvOverriding(EBlackboxEnv::Prod, EBlackboxEnv::Stress)); + + UNIT_ASSERT(NUtils::CheckBbEnvOverriding(EBlackboxEnv::ProdYateam, EBlackboxEnv::Prod)); + UNIT_ASSERT(NUtils::CheckBbEnvOverriding(EBlackboxEnv::ProdYateam, EBlackboxEnv::ProdYateam)); + UNIT_ASSERT(!NUtils::CheckBbEnvOverriding(EBlackboxEnv::ProdYateam, EBlackboxEnv::Test)); + UNIT_ASSERT(!NUtils::CheckBbEnvOverriding(EBlackboxEnv::ProdYateam, EBlackboxEnv::TestYateam)); + UNIT_ASSERT(!NUtils::CheckBbEnvOverriding(EBlackboxEnv::ProdYateam, EBlackboxEnv::Stress)); + + UNIT_ASSERT(NUtils::CheckBbEnvOverriding(EBlackboxEnv::Test, EBlackboxEnv::Prod)); + UNIT_ASSERT(NUtils::CheckBbEnvOverriding(EBlackboxEnv::Test, EBlackboxEnv::ProdYateam)); + UNIT_ASSERT(NUtils::CheckBbEnvOverriding(EBlackboxEnv::Test, EBlackboxEnv::Test)); + UNIT_ASSERT(NUtils::CheckBbEnvOverriding(EBlackboxEnv::Test, EBlackboxEnv::TestYateam)); + UNIT_ASSERT(NUtils::CheckBbEnvOverriding(EBlackboxEnv::Test, EBlackboxEnv::Stress)); + + UNIT_ASSERT(!NUtils::CheckBbEnvOverriding(EBlackboxEnv::TestYateam, EBlackboxEnv::Prod)); + UNIT_ASSERT(!NUtils::CheckBbEnvOverriding(EBlackboxEnv::TestYateam, EBlackboxEnv::ProdYateam)); + UNIT_ASSERT(NUtils::CheckBbEnvOverriding(EBlackboxEnv::TestYateam, EBlackboxEnv::Test)); + UNIT_ASSERT(NUtils::CheckBbEnvOverriding(EBlackboxEnv::TestYateam, EBlackboxEnv::TestYateam)); + UNIT_ASSERT(!NUtils::CheckBbEnvOverriding(EBlackboxEnv::TestYateam, EBlackboxEnv::Stress)); + + UNIT_ASSERT(!NUtils::CheckBbEnvOverriding(EBlackboxEnv::Stress, EBlackboxEnv::Prod)); + UNIT_ASSERT(!NUtils::CheckBbEnvOverriding(EBlackboxEnv::Stress, EBlackboxEnv::ProdYateam)); + UNIT_ASSERT(!NUtils::CheckBbEnvOverriding(EBlackboxEnv::Stress, EBlackboxEnv::Test)); + UNIT_ASSERT(!NUtils::CheckBbEnvOverriding(EBlackboxEnv::Stress, EBlackboxEnv::TestYateam)); + UNIT_ASSERT(NUtils::CheckBbEnvOverriding(EBlackboxEnv::Stress, EBlackboxEnv::Stress)); + } +} diff --git a/library/cpp/tvmauth/deprecated/service_context.cpp b/library/cpp/tvmauth/deprecated/service_context.cpp new file mode 100644 index 0000000000..208206a9dd --- /dev/null +++ b/library/cpp/tvmauth/deprecated/service_context.cpp @@ -0,0 +1,37 @@ +#include <library/cpp/tvmauth/checked_service_ticket.h> +#include <library/cpp/tvmauth/src/service_impl.h> + +namespace NTvmAuth { + static const char* EX_MSG = "ServiceContext already moved out"; + + TServiceContext::TServiceContext(TStringBuf secretBase64, TTvmId selfTvmId, TStringBuf tvmKeysResponse) + : Impl_(MakeHolder<TImpl>(secretBase64, selfTvmId, tvmKeysResponse)) + { + } + + TServiceContext::TServiceContext(TServiceContext&& o) = default; + TServiceContext& TServiceContext::operator=(TServiceContext&& o) = default; + TServiceContext::~TServiceContext() = default; + + TServiceContext TServiceContext::CheckingFactory(TTvmId selfTvmId, TStringBuf tvmKeysResponse) { + TServiceContext c; + c.Impl_ = MakeHolder<TImpl>(selfTvmId, tvmKeysResponse); + return c; + } + + TServiceContext TServiceContext::SigningFactory(TStringBuf secretBase64) { + TServiceContext c; + c.Impl_ = MakeHolder<TImpl>(secretBase64); + return c; + } + + TCheckedServiceTicket TServiceContext::Check(TStringBuf ticketBody, const TCheckFlags& flags) const { + Y_ENSURE(Impl_, EX_MSG); + return Impl_->Check(ticketBody, flags); + } + + TString TServiceContext::SignCgiParamsForTvm(TStringBuf ts, TStringBuf dst, TStringBuf scopes) const { + Y_ENSURE(Impl_, EX_MSG); + return Impl_->SignCgiParamsForTvm(ts, dst, scopes); + } +} diff --git a/library/cpp/tvmauth/deprecated/service_context.h b/library/cpp/tvmauth/deprecated/service_context.h new file mode 100644 index 0000000000..bdf1bb5224 --- /dev/null +++ b/library/cpp/tvmauth/deprecated/service_context.h @@ -0,0 +1,72 @@ +#pragma once + +#include <library/cpp/tvmauth/checked_service_ticket.h> + +#include <util/generic/ptr.h> + +namespace NTvmAuth { + class TServiceContext: public TAtomicRefCount<TServiceContext> { + public: + /*! + * @struct TCheckFlags holds flags that control checking + */ + struct TCheckFlags { + TCheckFlags() { + } + bool NeedDstCheck = true; + }; + + /*! + * Create service context. Serivce contexts are used to store TVM keys and parse service tickets. + * @param selfTvmId + * @param secretBase64 + * @param tvmKeysResponse + */ + TServiceContext(TStringBuf secretBase64, TTvmId selfTvmId, TStringBuf tvmKeysResponse); + TServiceContext(TServiceContext&&); + ~TServiceContext(); + + /*! + * Create service context only for checking service tickets + * \param[in] selfTvmId + * \param[in] tvmKeysResponse + * \return + */ + static TServiceContext CheckingFactory(TTvmId selfTvmId, TStringBuf tvmKeysResponse); + + /*! + * Create service context only for signing HTTP request to TVM-API + * \param[in] secretBase64 + * \return + */ + static TServiceContext SigningFactory(TStringBuf secretBase64); + + TServiceContext& operator=(TServiceContext&&); + + /*! + * Parse and validate service ticket body then create TCheckedServiceTicket object. + * @param ticketBody + * @return TCheckedServiceTicket object + */ + TCheckedServiceTicket Check(TStringBuf ticketBody, const TCheckFlags& flags = {}) const; + + /*! + * Sign params for TVM API + * @param ts Param 'ts' of request to TVM + * @param dst Param 'dst' of request to TVM + * @param scopes Param 'scopes' of request to TVM + * @return Signed string + */ + TString SignCgiParamsForTvm(TStringBuf ts, TStringBuf dst, TStringBuf scopes = TStringBuf()) const; + + class TImpl; + + private: + TServiceContext() = default; + + private: + THolder<TImpl> Impl_; + }; + + using TServiceContextPtr = TIntrusiveConstPtr<TServiceContext>; +} diff --git a/library/cpp/tvmauth/deprecated/user_context.cpp b/library/cpp/tvmauth/deprecated/user_context.cpp new file mode 100644 index 0000000000..712f622f1a --- /dev/null +++ b/library/cpp/tvmauth/deprecated/user_context.cpp @@ -0,0 +1,20 @@ +#include <library/cpp/tvmauth/checked_user_ticket.h> +#include <library/cpp/tvmauth/src/user_impl.h> + +namespace NTvmAuth { + static const char* EX_MSG = "UserContext already moved out"; + + TUserContext::TUserContext(EBlackboxEnv env, TStringBuf tvmKeysResponse) + : Impl_(MakeHolder<TImpl>(env, tvmKeysResponse)) + { + } + + TUserContext::TUserContext(TUserContext&& o) = default; + TUserContext& TUserContext::operator=(TUserContext&& o) = default; + TUserContext::~TUserContext() = default; + + TCheckedUserTicket TUserContext::Check(TStringBuf ticketBody) const { + Y_ENSURE(Impl_, EX_MSG); + return Impl_->Check(ticketBody); + } +} diff --git a/library/cpp/tvmauth/deprecated/user_context.h b/library/cpp/tvmauth/deprecated/user_context.h new file mode 100644 index 0000000000..f7fe67d02e --- /dev/null +++ b/library/cpp/tvmauth/deprecated/user_context.h @@ -0,0 +1,30 @@ +#pragma once + +#include <library/cpp/tvmauth/checked_user_ticket.h> + +#include <util/generic/ptr.h> + +namespace NTvmAuth { + class TUserContext: public TAtomicRefCount<TUserContext> { + public: + TUserContext(EBlackboxEnv env, TStringBuf tvmKeysResponse); + TUserContext(TUserContext&&); + ~TUserContext(); + + TUserContext& operator=(TUserContext&&); + + /*! + * Parse and validate user ticket body then create TCheckedUserTicket object. + * @param ticketBody + * @return TCheckedUserTicket object + */ + TCheckedUserTicket Check(TStringBuf ticketBody) const; + + class TImpl; + + private: + THolder<TImpl> Impl_; + }; + + using TUserContextPtr = TIntrusiveConstPtr<TUserContext>; +} diff --git a/library/cpp/tvmauth/exception.h b/library/cpp/tvmauth/exception.h new file mode 100644 index 0000000000..f528886b95 --- /dev/null +++ b/library/cpp/tvmauth/exception.h @@ -0,0 +1,20 @@ +#pragma once + +#include <util/generic/yexception.h> + +#include <exception> + +namespace NTvmAuth { + class TTvmException: public yexception { + }; + class TContextException: public TTvmException { + }; + class TMalformedTvmSecretException: public TContextException { + }; + class TMalformedTvmKeysException: public TContextException { + }; + class TEmptyTvmKeysException: public TContextException { + }; + class TNotAllowedException: public TTvmException { + }; +} diff --git a/library/cpp/tvmauth/src/parser.cpp b/library/cpp/tvmauth/src/parser.cpp new file mode 100644 index 0000000000..358de58d36 --- /dev/null +++ b/library/cpp/tvmauth/src/parser.cpp @@ -0,0 +1,97 @@ +#include "parser.h" + +#include "utils.h" + +#include <library/cpp/tvmauth/exception.h> + +#include <util/generic/strbuf.h> +#include <util/string/split.h> + +#include <ctime> + +namespace NTvmAuth { + TString TParserTvmKeys::ParseStrV1(TStringBuf str) { + while (str && str.back() == '\n') { + str.Chop(1); + } + + TStringBuf ver = str.NextTok(DELIM); + if (!str || !ver || ver != "1") { + throw TMalformedTvmKeysException() << "Malformed TVM keys"; + } + TString res = NUtils::Base64url2bin(str); + if (res.empty()) { + throw TMalformedTvmKeysException() << "Malformed TVM keys"; + } + return res; + } + + TStringBuf TParserTickets::UserFlag() { + static const char BUF_[] = "user"; + return TStringBuf(BUF_, sizeof(BUF_) - 1); + } + + TStringBuf TParserTickets::ServiceFlag() { + static const char BUF_[] = "serv"; + return TStringBuf(BUF_, sizeof(BUF_) - 1); + } + + TParserTickets::TRes TParserTickets::ParseV3(TStringBuf body, const NRw::TPublicKeys& keys, TStringBuf type) { + TStrRes str = ParseStrV3(body, type); + TRes res(str.Status); + if (str.Status != ETicketStatus::Ok) { + return TRes(str.Status); + } + if (!res.Ticket.ParseFromString(str.Proto)) { + res.Status = ETicketStatus::Malformed; + return res; + } + if (res.Ticket.expirationtime() <= time(nullptr)) { + res.Status = ETicketStatus::Expired; + return res; + } + + auto itKey = keys.find(res.Ticket.keyid()); + if (itKey == keys.end()) { + res.Status = ETicketStatus::MissingKey; + return res; + } + if (!itKey->second.CheckSign(str.ForCheck, str.Sign)) { + res.Status = ETicketStatus::SignBroken; + return res; + } + return res; + } + + TParserTickets::TStrRes TParserTickets::ParseStrV3(TStringBuf body, TStringBuf type) { + TStringBuf forCheck = body; + TStringBuf version = body.NextTok(DELIM); + if (!body || version.size() != 1) { + return {ETicketStatus::Malformed, {}, {}, {}}; + } + if (version != "3") { + return {ETicketStatus::UnsupportedVersion, {}, {}, {}}; + } + + TStringBuf ticketType = body.NextTok(DELIM); + if (ticketType != type) { + return {ETicketStatus::InvalidTicketType, {}, {}, {}}; + } + + TStringBuf proto = body.NextTok(DELIM); + TStringBuf sign = body.NextTok(DELIM); + + if (!proto || !sign || body.size() > 0) { + return {ETicketStatus::Malformed, {}, {}, {}}; + } + + TString protoBin = NUtils::Base64url2bin(proto); + TString signBin = NUtils::Base64url2bin(sign); + + if (!protoBin || !signBin) { + return {ETicketStatus::Malformed, {}, {}, {}}; + } + + return {ETicketStatus::Ok, std::move(protoBin), std::move(signBin), forCheck.Chop(sign.size())}; + } +} diff --git a/library/cpp/tvmauth/src/parser.h b/library/cpp/tvmauth/src/parser.h new file mode 100644 index 0000000000..678e709444 --- /dev/null +++ b/library/cpp/tvmauth/src/parser.h @@ -0,0 +1,51 @@ +#pragma once + +#include <library/cpp/tvmauth/src/protos/ticket2.pb.h> +#include <library/cpp/tvmauth/src/rw/keys.h> + +#include <library/cpp/tvmauth/ticket_status.h> + +#include <util/generic/fwd.h> + +#include <string> + +namespace NTvmAuth { + struct TParserTvmKeys { + static inline const char DELIM = ':'; + static TString ParseStrV1(TStringBuf str); + }; + + struct TParserTickets { + static const char DELIM = ':'; + + static TStringBuf UserFlag(); + static TStringBuf ServiceFlag(); + + struct TRes { + TRes(ETicketStatus status) + : Status(status) + { + } + + ETicketStatus Status; + + ticket2::Ticket Ticket; + }; + static TRes ParseV3(TStringBuf body, const NRw::TPublicKeys& keys, TStringBuf type); + + // private: + struct TStrRes { + const ETicketStatus Status; + + TString Proto; + TString Sign; + + TStringBuf ForCheck; + + bool operator==(const TStrRes& o) const { // for tests + return Status == o.Status && Proto == o.Proto && Sign == o.Sign && ForCheck == o.ForCheck; + } + }; + static TStrRes ParseStrV3(TStringBuf body, TStringBuf type); + }; +} diff --git a/library/cpp/tvmauth/src/protos/ticket2.proto b/library/cpp/tvmauth/src/protos/ticket2.proto new file mode 100644 index 0000000000..66c00a7d01 --- /dev/null +++ b/library/cpp/tvmauth/src/protos/ticket2.proto @@ -0,0 +1,31 @@ +package ticket2; + +option go_package = "a.yandex-team.ru/library/cpp/tvmauth/src/protos"; + +import "library/cpp/tvmauth/src/protos/tvm_keys.proto"; + +message User { + required uint64 uid = 1; +} + +message UserTicket { + repeated User users = 1; + required uint64 defaultUid = 2; + repeated string scopes = 3; + required uint32 entryPoint = 4; + required tvm_keys.BbEnvType env = 5; +} + +message ServiceTicket { + required uint32 srcClientId = 1; + required uint32 dstClientId = 2; + repeated string scopes = 3; + optional uint64 issuerUid = 4; +} + +message Ticket { + required uint32 keyId = 1; + required int64 expirationTime = 2; + optional UserTicket user = 3; + optional ServiceTicket service = 4; +} diff --git a/library/cpp/tvmauth/src/protos/tvm_keys.proto b/library/cpp/tvmauth/src/protos/tvm_keys.proto new file mode 100644 index 0000000000..9ba42dbf80 --- /dev/null +++ b/library/cpp/tvmauth/src/protos/tvm_keys.proto @@ -0,0 +1,36 @@ +package tvm_keys; + +option go_package = "a.yandex-team.ru/library/cpp/tvmauth/src/protos"; + +enum KeyType { + RabinWilliams = 0; +} + +enum BbEnvType { + Prod = 0; + Test = 1; + ProdYateam = 2; + TestYateam = 3; + Stress = 4; +} + +message General { + required uint32 id = 1; + required KeyType type = 2; + required bytes body = 3; + optional int64 createdTime = 4; +} + +message BbKey { + required General gen = 1; + required BbEnvType env = 2; +} + +message TvmKey { + required General gen = 1; +} + +message Keys { + repeated BbKey bb = 1; + repeated TvmKey tvm = 2; +} diff --git a/library/cpp/tvmauth/src/rw/keys.cpp b/library/cpp/tvmauth/src/rw/keys.cpp new file mode 100644 index 0000000000..5fba7b9232 --- /dev/null +++ b/library/cpp/tvmauth/src/rw/keys.cpp @@ -0,0 +1,138 @@ +#include "keys.h" + +#include "rw.h" + +#include <library/cpp/openssl/init/init.h> + +#include <openssl/evp.h> + +#include <util/generic/strbuf.h> +#include <util/generic/yexception.h> + +namespace { + struct TInit { + TInit() { + InitOpenSSL(); + } + } INIT; +} + +namespace NTvmAuth { + namespace NRw { + namespace NPrivate { + void TRwDestroyer::Destroy(TRwInternal* o) { + RwFree(o); + } + + class TArrayDestroyer { + public: + static void Destroy(unsigned char* o) { + free(o); + } + }; + } + + static TString SerializeRW(TRwKey* rw, int (*func)(const TRwKey*, unsigned char**)) { + unsigned char* buf = nullptr; + int size = func(rw, &buf); + THolder<unsigned char, NPrivate::TArrayDestroyer> guard(buf); + return TString((char*)buf, size); + } + + TKeyPair GenKeyPair(size_t size) { + TRw rw(RwNew()); + RwGenerateKey(rw.Get(), size); + + TRw skey(RwPrivateKeyDup(rw.Get())); + TRw vkey(RwPublicKeyDup(rw.Get())); + + TKeyPair res; + res.Private = SerializeRW(skey.Get(), &i2d_RWPrivateKey); + res.Public = SerializeRW(vkey.Get(), &i2d_RWPublicKey); + + TRwPrivateKey prKey(res.Private, 0); + TRwPublicKey pubKey(res.Public); + + const TStringBuf msg = "Test test test test test"; + + Y_ENSURE(pubKey.CheckSign(msg, prKey.SignTicket(msg)), "Failed to gen keys"); + + return res; + } + + TRwPrivateKey::TRwPrivateKey(TStringBuf body, TKeyId id) + : Id_(id) + , Rw_(Deserialize(body)) + , SignLen_(RwModSize(Rw_.Get())) + { + Y_ENSURE(SignLen_ > 0, "Private key has bad len: " << SignLen_); + } + + TKeyId TRwPrivateKey::GetId() const { + return Id_; + } + + TString TRwPrivateKey::SignTicket(TStringBuf ticket) const { + TString res(SignLen_, 0x00); + + int len = RwPssrSignMsg(ticket.size(), + (const unsigned char*)ticket.data(), + (unsigned char*)res.data(), + Rw_.Get(), + (EVP_MD*)EVP_sha256()); + + Y_ENSURE(len > 0 && len <= SignLen_, "Signing failed. len: " << len); + + res.resize(len); + return res; + } + + TRw TRwPrivateKey::Deserialize(TStringBuf key) { + TRwKey* rw = nullptr; + auto data = reinterpret_cast<const unsigned char*>(key.data()); + if (!d2i_RWPrivateKey(&rw, &data, key.size())) { + ythrow yexception() << "Private key is malformed"; + } + return TRw(rw); + } + + TRwPublicKey::TRwPublicKey(TStringBuf body) + : Rw_(Deserialize(body)) + { + } + + bool TRwPublicKey::CheckSign(TStringBuf ticket, TStringBuf sign) const { + int result = RwPssrVerifyMsg(ticket.size(), + (const unsigned char*)ticket.data(), + (unsigned char*)sign.data(), + sign.size(), + Rw_.Get(), + (EVP_MD*)EVP_sha256()); + + Y_ENSURE(result >= 0, "Failed to check sign: " << result); + return result; + } + + TRw TRwPublicKey::Deserialize(TStringBuf key) { + TRwKey* rw = nullptr; + auto data = reinterpret_cast<const unsigned char*>(key.data()); + auto status = d2i_RWPublicKey(&rw, &data, key.size()); + + TRw res(rw); + Y_ENSURE(status, "Public key is malformed: " << key); + return res; + } + + TSecureHeap::TSecureHeap(size_t totalSize, int minChunkSize) { + CRYPTO_secure_malloc_init(totalSize, minChunkSize); + } + + TSecureHeap::~TSecureHeap() { + CRYPTO_secure_malloc_done(); + } + + void TSecureHeap::Init(size_t totalSize, int minChunkSize) { + Singleton<TSecureHeap>(totalSize, minChunkSize); + } + } +} diff --git a/library/cpp/tvmauth/src/rw/keys.h b/library/cpp/tvmauth/src/rw/keys.h new file mode 100644 index 0000000000..e02b7e72a1 --- /dev/null +++ b/library/cpp/tvmauth/src/rw/keys.h @@ -0,0 +1,65 @@ +#pragma once + +#include <util/generic/ptr.h> +#include <util/generic/string.h> + +#include <unordered_map> + +struct TRwInternal; + +namespace NTvmAuth { + namespace NRw { + namespace NPrivate { + class TRwDestroyer { + public: + static void Destroy(TRwInternal* o); + }; + } + + using TRw = THolder<TRwInternal, NPrivate::TRwDestroyer>; + using TKeyId = ui32; + + struct TKeyPair { + TString Private; + TString Public; + }; + TKeyPair GenKeyPair(size_t size); + + class TRwPrivateKey { + public: + TRwPrivateKey(TStringBuf body, TKeyId id); + + TKeyId GetId() const; + TString SignTicket(TStringBuf ticket) const; + + private: + static TRw Deserialize(TStringBuf key); + + TKeyId Id_; + TRw Rw_; + int SignLen_; + }; + + class TRwPublicKey { + public: + TRwPublicKey(TStringBuf body); + + bool CheckSign(TStringBuf ticket, TStringBuf sign) const; + + private: + static TRw Deserialize(TStringBuf key); + + TRw Rw_; + }; + + using TPublicKeys = std::unordered_map<TKeyId, TRwPublicKey>; + + class TSecureHeap { + public: + TSecureHeap(size_t totalSize, int minChunkSize); + ~TSecureHeap(); + + static void Init(size_t totalSize = 16 * 1024 * 1024, int minChunkSize = 16); + }; + } +} diff --git a/library/cpp/tvmauth/src/rw/rw.h b/library/cpp/tvmauth/src/rw/rw.h new file mode 100644 index 0000000000..aee49148eb --- /dev/null +++ b/library/cpp/tvmauth/src/rw/rw.h @@ -0,0 +1,86 @@ +#pragma once + +#include <openssl/bn.h> +#include <openssl/crypto.h> + +#ifdef __cplusplus +extern "C" { +#endif + + typedef struct { + BIGNUM* S; + } TRwSignature; + + /*Rabin–Williams*/ + typedef struct TRwInternal TRwKey; + + typedef struct { + TRwSignature* (*RwSign)(const unsigned char* dgst, const int dlen, TRwKey* rw); + int (*RwVerify)(const unsigned char* dgst, int dgst_len, TRwSignature* sig, const TRwKey* rw); + int (*RwApply)(BIGNUM* r, BIGNUM* x, BN_CTX* ctx, const TRwKey* rw); + } TRwMethod; + + struct TRwInternal { + /* first private multiplier */ + BIGNUM* P; + /* second private multiplier */ + BIGNUM* Q; + /* n = p*q - RW modulus */ + BIGNUM* N; + /* precomputed 2^((3q-5)/8) mod q */ + BIGNUM* Twomq; + /* precomputed 2^((9p-11)/8) mod p*/ + BIGNUM* Twomp; + /* precomputed q^(p-2) == q^(-1) mod p */ + BIGNUM* Iqmp; + /* (q+1) / 8 */ + BIGNUM* Dq; + /* (p-3) / 8 */ + BIGNUM* Dp; + /* functions for working with RW */ + const TRwMethod* Meth; + }; + + TRwSignature* RwSignatureNew(void); + void RwSignatureFree(TRwSignature* a); + + /* RW signing functions */ + /* the function can put some tmp values to rw */ + int RwPssrSignHash(const unsigned char* from, unsigned char* to, TRwKey* rw, const EVP_MD* md); + int RwPssrSignMsg(const int msgLen, const unsigned char* msg, unsigned char* to, TRwKey* rw, const EVP_MD* md); + + /* RW-PSS verification functions */ + int RwPssrVerifyHash(const unsigned char* from, const unsigned char* sig, const int sig_len, const TRwKey* rw, const EVP_MD* md); + int RwPssrVerifyMsg(const int msgLen, const unsigned char* msg, const unsigned char* sig, const int sig_len, const TRwKey* rw, const EVP_MD* md); + + /* internal functions, use them only if you know what you're doing */ + int RwNoPaddingSign(int flen, const unsigned char* from, unsigned char* to, TRwKey* rw); + int RwApply(const int flen, const unsigned char* from, unsigned char* to, const TRwKey* rw); + + const TRwMethod* RwDefaultMethods(void); + + TRwKey* RwNew(void); + void RwFree(TRwKey* r); + int RwSize(const TRwKey* rw); + int RwModSize(const TRwKey* rw); + + TRwKey* RwPublicKeyDup(TRwKey* rw); + TRwKey* RwPrivateKeyDup(TRwKey* rw); + + // NOLINTNEXTLINE(readability-identifier-naming) + TRwKey* d2i_RWPublicKey(TRwKey** a, const unsigned char** pp, long length); + // NOLINTNEXTLINE(readability-identifier-naming) + TRwKey* d2i_RWPrivateKey(TRwKey** a, const unsigned char** pp, long length); + + int RwGenerateKey(TRwKey* a, int bits); + // NOLINTNEXTLINE(readability-identifier-naming) + int i2d_RWPublicKey(const TRwKey* a, unsigned char** pp); + // NOLINTNEXTLINE(readability-identifier-naming) + int i2d_RWPrivateKey(const TRwKey* a, unsigned char** pp); + + int RwPaddingAddPssr(const TRwKey* rw, unsigned char* EM, const unsigned char* mHash, const EVP_MD* Hash, int sLen); + int RwVerifyPssr(const TRwKey* rw, const unsigned char* mHash, const EVP_MD* Hash, const unsigned char* EM, int sLen); + +#ifdef __cplusplus +} +#endif diff --git a/library/cpp/tvmauth/src/rw/rw_asn1.c b/library/cpp/tvmauth/src/rw/rw_asn1.c new file mode 100644 index 0000000000..f9dfe3996d --- /dev/null +++ b/library/cpp/tvmauth/src/rw/rw_asn1.c @@ -0,0 +1,81 @@ +#include "rw.h" + +#include <openssl/asn1.h> +#include <openssl/asn1t.h> +#include <openssl/rand.h> + +#include <stdio.h> + +/* Override the default new methods */ +/* This callback is used by OpenSSL's ASN.1 parser */ +static int SignatureCallback(int operation, ASN1_VALUE** pval, const ASN1_ITEM* it, void* exarg) { + (void)it; + (void)exarg; + + if (operation == ASN1_OP_NEW_PRE) { + TRwSignature* sig; + sig = OPENSSL_malloc(sizeof(TRwSignature)); + if (!sig) + return 0; + sig->S = NULL; + *pval = (ASN1_VALUE*)sig; + return 2; + } + return 1; +} + +/* ASN.1 structure representing RW signature value */ +ASN1_SEQUENCE_cb(TRwSignature, SignatureCallback) = { + ASN1_SIMPLE(TRwSignature, S, BIGNUM), +} ASN1_SEQUENCE_END_cb(TRwSignature, TRwSignature) + + /* i2d_ and d2i functions implementation for RW */ + IMPLEMENT_ASN1_ENCODE_FUNCTIONS_const_fname(TRwSignature, TRwSignature, TRwSignature) + + /* Override the default free and new methods */ + static int RwCallback(int operation, ASN1_VALUE** pval, const ASN1_ITEM* it, void* exarg) { + (void)it; + (void)exarg; + + if (operation == ASN1_OP_NEW_PRE) { + *pval = (ASN1_VALUE*)RwNew(); + if (*pval) + return 2; + return 0; + } else if (operation == ASN1_OP_FREE_PRE) { + RwFree((TRwKey*)*pval); + *pval = NULL; + return 2; + } + return 1; +} + +/* ASN.1 representation of RW's private key */ +ASN1_SEQUENCE_cb(RWPrivateKey, RwCallback) = { + ASN1_SIMPLE(TRwKey, N, BIGNUM), + ASN1_SIMPLE(TRwKey, P, CBIGNUM), + ASN1_SIMPLE(TRwKey, Q, CBIGNUM), + ASN1_SIMPLE(TRwKey, Iqmp, CBIGNUM), + ASN1_SIMPLE(TRwKey, Dq, CBIGNUM), + ASN1_SIMPLE(TRwKey, Dp, CBIGNUM), + ASN1_SIMPLE(TRwKey, Twomp, CBIGNUM), + ASN1_SIMPLE(TRwKey, Twomq, CBIGNUM)} ASN1_SEQUENCE_END_cb(TRwKey, RWPrivateKey); + +/* i2d_ and d2i_ functions for RW's private key */ +IMPLEMENT_ASN1_ENCODE_FUNCTIONS_const_fname(TRwKey, RWPrivateKey, RWPrivateKey); + +/* ASN.1 representation of RW public key */ +ASN1_SEQUENCE_cb(RWPublicKey, RwCallback) = { + ASN1_SIMPLE(TRwKey, N, BIGNUM), +} ASN1_SEQUENCE_END_cb(TRwKey, RWPublicKey); + +/* i2d_ and d2i functions for RW public key */ +IMPLEMENT_ASN1_ENCODE_FUNCTIONS_const_fname(TRwKey, RWPublicKey, RWPublicKey); + +TRwKey* RwPublicKeyDup(TRwKey* rw) { + return ASN1_item_dup(ASN1_ITEM_rptr(RWPublicKey), rw); +} + +TRwKey* RwPrivateKeyDup(TRwKey* rw) { + return ASN1_item_dup(ASN1_ITEM_rptr(RWPrivateKey), rw); +} diff --git a/library/cpp/tvmauth/src/rw/rw_key.c b/library/cpp/tvmauth/src/rw/rw_key.c new file mode 100644 index 0000000000..78baaf4dd9 --- /dev/null +++ b/library/cpp/tvmauth/src/rw/rw_key.c @@ -0,0 +1,135 @@ +#include "rw.h" + +#include <openssl/rand.h> + +int RwGenerateKey(TRwKey* rw, int bits) { + int ok = 0; + + BN_CTX* ctx = NULL; + BIGNUM *rem3 = NULL, *rem7 = NULL, *mod8 = NULL, *rem5 = NULL; + BIGNUM *nmod = NULL, *twomqexp = NULL, *twompexp = NULL, *two = NULL; + + int bitsp = (bits + 1) / 2; + int bitsq = bits - bitsp; + + /* make sure that all components are not null */ + if ((ctx = BN_CTX_secure_new()) == NULL) + goto err; + if (!rw) + goto err; + if (!rw->N && ((rw->N = BN_new()) == NULL)) + goto err; + if (!rw->P && ((rw->P = BN_new()) == NULL)) + goto err; + if (!rw->Q && ((rw->Q = BN_new()) == NULL)) + goto err; + if (!rw->Iqmp && ((rw->Iqmp = BN_new()) == NULL)) + goto err; + if (!rw->Twomq && ((rw->Twomq = BN_new()) == NULL)) + goto err; + if (!rw->Twomp && ((rw->Twomp = BN_new()) == NULL)) + goto err; + if (!rw->Dq && ((rw->Dq = BN_new()) == NULL)) + goto err; + if (!rw->Dp && ((rw->Dp = BN_new()) == NULL)) + goto err; + + BN_CTX_start(ctx); + + rem3 = BN_CTX_get(ctx); + rem7 = BN_CTX_get(ctx); + rem5 = BN_CTX_get(ctx); + mod8 = BN_CTX_get(ctx); + nmod = BN_CTX_get(ctx); + twomqexp = BN_CTX_get(ctx); + twompexp = BN_CTX_get(ctx); + two = BN_CTX_get(ctx); + + if (!BN_set_word(mod8, 8)) + goto err; + if (!BN_set_word(rem3, 3)) + goto err; + if (!BN_set_word(rem7, 7)) + goto err; + if (!BN_set_word(rem5, 5)) + goto err; + if (!BN_set_word(two, 2)) + goto err; + + /* generate p */ + /* add == 8 */ + /* rem == 3 */ + /* safe == 0 as we don't need (p-1)/2 to be also prime */ + if (!BN_generate_prime_ex(rw->P, bitsp, 0, mod8, rem3, NULL)) + goto err; + + /* generate q */ + /* add == 8 */ + /* rem == 7 */ + /* safe == 0 */ + if (!BN_generate_prime_ex(rw->Q, bitsq, 0, mod8, rem7, NULL)) + goto err; + + /* n == p*q */ + if (!BN_mul(rw->N, rw->P, rw->Q, ctx)) + goto err; + + /* n == 5 mod 8 ? */ + if (!BN_nnmod(nmod, rw->N, mod8, ctx)) + goto err; + if (BN_ucmp(rem5, nmod) != 0) + goto err; + + /* q^(-1) mod p */ + if (!BN_mod_inverse(rw->Iqmp, rw->Q, rw->P, ctx)) + goto err; + + /* twomqexp = (3q-5)/8 */ + if (!BN_copy(twomqexp, rw->Q)) + goto err; + if (!BN_mul_word(twomqexp, 3)) + goto err; + if (!BN_sub_word(twomqexp, 5)) + goto err; + if (!BN_rshift(twomqexp, twomqexp, 3)) + goto err; + if (!BN_mod_exp(rw->Twomq, two, twomqexp, rw->Q, ctx)) + goto err; + + /* twompexp = (9p-11)/8 */ + if (!BN_copy(twompexp, rw->P)) + goto err; + if (!BN_mul_word(twompexp, 9)) + goto err; + if (!BN_sub_word(twompexp, 11)) + goto err; + if (!BN_rshift(twompexp, twompexp, 3)) + goto err; + if (!BN_mod_exp(rw->Twomp, two, twompexp, rw->P, ctx)) + goto err; + + /* dp = (p-3) / 8 */ + if (!BN_copy(rw->Dp, rw->P)) + goto err; + if (!BN_sub_word(rw->Dp, 3)) + goto err; + if (!BN_rshift(rw->Dp, rw->Dp, 3)) + goto err; + + /* dq = (q+1) / 8 */ + if (!BN_copy(rw->Dq, rw->Q)) + goto err; + if (!BN_add_word(rw->Dq, 1)) + goto err; + if (!BN_rshift(rw->Dq, rw->Dq, 3)) + goto err; + + ok = 1; + +err: + if (ctx != NULL) { + BN_CTX_end(ctx); + BN_CTX_free(ctx); + } + return ok; +} diff --git a/library/cpp/tvmauth/src/rw/rw_lib.c b/library/cpp/tvmauth/src/rw/rw_lib.c new file mode 100644 index 0000000000..afd73da38b --- /dev/null +++ b/library/cpp/tvmauth/src/rw/rw_lib.c @@ -0,0 +1,77 @@ +#include "rw.h" + +#include <openssl/asn1.h> + +#include <stdio.h> + +TRwKey* RwNew(void) { + TRwKey* ret = NULL; + + ret = (TRwKey*)malloc(sizeof(TRwKey)); + if (ret == NULL) { + return (NULL); + } + ret->Meth = RwDefaultMethods(); + + ret->P = NULL; + ret->Q = NULL; + ret->N = NULL; + ret->Iqmp = NULL; + ret->Twomq = NULL; + ret->Twomp = NULL; + ret->Dp = NULL; + ret->Dq = NULL; + + return ret; +} + +void RwFree(TRwKey* r) { + if (r == NULL) + return; + + if (r->P != NULL) + BN_clear_free(r->P); + if (r->Q != NULL) + BN_clear_free(r->Q); + if (r->N != NULL) + BN_clear_free(r->N); + if (r->Iqmp != NULL) + BN_clear_free(r->Iqmp); + if (r->Dp != NULL) + BN_clear_free(r->Dp); + if (r->Dq != NULL) + BN_clear_free(r->Dq); + if (r->Twomp != NULL) + BN_clear_free(r->Twomp); + if (r->Twomq != NULL) + BN_clear_free(r->Twomq); + + free(r); +} + +int RwSize(const TRwKey* r) { + int ret = 0, i = 0; + ASN1_INTEGER bs; + unsigned char buf[4]; /* 4 bytes looks really small. + However, i2d_ASN1_INTEGER() will not look + beyond the first byte, as long as the second + parameter is NULL. */ + + i = BN_num_bits(r->N); + bs.length = (i + 7) / 8; + bs.data = buf; + bs.type = V_ASN1_INTEGER; + /* If the top bit is set the asn1 encoding is 1 larger. */ + buf[0] = 0xff; + + i = i2d_ASN1_INTEGER(&bs, NULL); + + ret = ASN1_object_size(1, i, V_ASN1_SEQUENCE); + return ret; +} + +int RwModSize(const TRwKey* rw) { + if (rw == NULL || rw->N == NULL) + return 0; + return BN_num_bytes(rw->N); +} diff --git a/library/cpp/tvmauth/src/rw/rw_ossl.c b/library/cpp/tvmauth/src/rw/rw_ossl.c new file mode 100644 index 0000000000..85bcb802e9 --- /dev/null +++ b/library/cpp/tvmauth/src/rw/rw_ossl.c @@ -0,0 +1,481 @@ +#include "rw.h" + +#include <openssl/rand.h> + +//#define RW_PRINT_DEBUG +//#define AVOID_IF +//#define FAULT_TOLERANCE_CHECK + +#ifdef RW_PRINT_DEBUG + #include <stdio.h> +#endif + +static TRwSignature* RwDoSign(const unsigned char* dgst, int dlen, TRwKey* rw); +static int RwDoVerify(const unsigned char* dgst, int dgst_len, TRwSignature* sig, const TRwKey* rw); +static int RwDoApply(BIGNUM* r, BIGNUM* x, BN_CTX* ctx, const TRwKey* rw); + +static TRwMethod rw_default_meth = { + RwDoSign, + RwDoVerify, + RwDoApply}; + +const TRwMethod* RwDefaultMethods(void) { + return &rw_default_meth; +} + +#ifdef RW_PRINT_DEBUG + +static void print_bn(char* name, BIGNUM* value) { + char* str_repr; + str_repr = BN_bn2dec(value); + printf("Name: %s\n", name); + printf("Value: %s\n", str_repr); + OPENSSL_free(str_repr); +} + + #define DEBUG_PRINT_BN(s, x) \ + do { \ + print_bn((s), (x)); \ + } while (0); + #define DEBUG_PRINT_RW(r) \ + do { \ + DEBUG_PRINT_BN("rw->p", (r)->p); \ + DEBUG_PRINT_BN("rw->q", (r)->q); \ + DEBUG_PRINT_BN("rw->n", (r)->n); \ + DEBUG_PRINT_BN("rw->iqmp", (r)->iqmp); \ + DEBUG_PRINT_BN("rw->twomp", (r)->twomp); \ + DEBUG_PRINT_BN("rw->twomq", (r)->twomq); \ + DEBUG_PRINT_BN("rw->dp", (r)->dp); \ + DEBUG_PRINT_BN("rw->dq", (r)->dq); \ + } while (0); + #define DEBUG_PRINTF(s, v) \ + do { \ + printf((s), (v)); \ + } while (0); +#else + #define DEBUG_PRINT_BN(s, x) + #define DEBUG_PRINT_RW(r) + #define DEBUG_PRINTF(s, v) +#endif + +/* + * The algorithms was taken from + * https://cr.yp.to/sigs/rwsota-20080131.pdf + * Section 6 -> "Avoiding Jacobi symbols" + * '^' means power + * 1. Compute U = h ^ ((q+1) / 8) mod q + * 2. If U ^ 4 - h mod q == 0, set e = 1 otherwise set e = -1 + * 3. Compute V = (eh) ^ ((p-3)/8) mod p + * 4. If (V^4 * (eh)^2 - eh) mod p = 0; set f = 1; otherwise set f = 2 + * 5. Precompute 2^((3q-5) / 8) mod q; Compute W = f^((3*q - 5) / 8) * U mod q + * 6. Precompute 2^((9p-11) / 8) mod p; Compute X = f^((9p-11) / 8) * V^3 * eh mod p + * 7. Precompute q^(p-2) mod p; Compute Y = W + q(q^(p-2) * (X - W) mod p) + * 8. Compute s = Y^2 mod pq + * 9. Fault tolerance: if efs^2 mod pq != h start over + */ +static TRwSignature* RwDoSign(const unsigned char* dgst, int dlen, TRwKey* rw) { + BIGNUM *m, *U, *V, *tmp, *m_q, *m_p, *tmp2; + /* additional variables to avoid "if" statements */ + BIGNUM* tmp_mp; + TRwSignature* ret = NULL; + BN_CTX* ctx = NULL; + int ok = 0, e = 0, f = 0; + +#ifdef AVOID_IF + /* additional variables to avoid "if" statements */ + BIGNUM *tmp_U, *tmp_V; +#endif + + if (!rw || !rw->P || !rw->Q || !rw->N || !rw->Iqmp || !rw->Dp || !rw->Dq || !rw->Twomp || !rw->Twomq) + goto err; + + if ((ctx = BN_CTX_secure_new()) == NULL) + goto err; + BN_CTX_start(ctx); + + m = BN_CTX_get(ctx); + U = BN_CTX_get(ctx); + V = BN_CTX_get(ctx); + tmp = BN_CTX_get(ctx); + tmp2 = BN_CTX_get(ctx); + m_q = BN_CTX_get(ctx); + m_p = BN_CTX_get(ctx); + tmp_mp = BN_CTX_get(ctx); + +#ifdef AVOID_IF + tmp_U = BN_CTX_get(ctx); + tmp_V = BN_CTX_get(ctx); +#endif + + DEBUG_PRINT_RW(rw) + + /* if (!BN_set_word(four, 4)) goto err; */ + + if (!BN_bin2bn(dgst, dlen, m)) + goto err; + if (BN_ucmp(m, rw->N) >= 0) + goto err; + + /* check if m % 16 == 12 */ + if (BN_mod_word(m, 16) != 12) + goto err; + DEBUG_PRINT_BN("m", m) + + /* TODO: optimization to avoid memory allocation? */ + if ((ret = RwSignatureNew()) == NULL) + goto err; + /* memory allocation */ + if ((ret->S = BN_new()) == NULL) + goto err; + + /* m_q = m mod q */ + if (!BN_nnmod(m_q, m, rw->Q, ctx)) + goto err; + /* m_p = m mod p */ + if (!BN_nnmod(m_p, m, rw->P, ctx)) + goto err; + + DEBUG_PRINT_BN("m_p", m_p) + DEBUG_PRINT_BN("m_q", m_q) + + /* U = h ** ((q+1)/8) mod q */ + if (!BN_mod_exp(U, m_q, rw->Dq, rw->Q, ctx)) + goto err; + DEBUG_PRINT_BN("U", U) + + /* tmp = U^4 - h mod q */ + if (!BN_mod_sqr(tmp, U, rw->Q, ctx)) + goto err; + if (!BN_mod_sqr(tmp, tmp, rw->Q, ctx)) + goto err; + DEBUG_PRINT_BN("U**4 mod q", tmp) + + /* e = 1 if tmp == 0 else -1 */ + e = 2 * (BN_ucmp(tmp, m_q) == 0) - 1; + DEBUG_PRINTF("e == %i\n", e) + + /* + to avoid "if" branch + if e == -1: m_p = tmp_mp + if e == 1: m_p = m_p + */ + if (!BN_sub(tmp_mp, rw->P, m_p)) + goto err; + m_p = (BIGNUM*)((1 - ((1 + e) >> 1)) * (BN_ULONG)tmp_mp + ((1 + e) >> 1) * (BN_ULONG)m_p); + DEBUG_PRINT_BN("eh mod p", m_p) + + /* V = (eh) ** ((p-3)/8) */ + if (!BN_mod_exp(V, m_p, rw->Dp, rw->P, ctx)) + goto err; + DEBUG_PRINT_BN("V == ((eh) ** ((p-3)/8))", V) + + /* (eh) ** 2 */ + if (!BN_mod_sqr(tmp2, m_p, rw->P, ctx)) + goto err; + DEBUG_PRINT_BN("(eh)**2", tmp2) + + /* V ** 4 */ + if (!BN_mod_sqr(tmp, V, rw->P, ctx)) + goto err; + if (!BN_mod_sqr(tmp, tmp, rw->P, ctx)) + goto err; + DEBUG_PRINT_BN("V**4", tmp) + + /* V**4 * (eh)**2 */ + if (!BN_mod_mul(tmp, tmp, tmp2, rw->P, ctx)) + goto err; + DEBUG_PRINT_BN("tmp = (V**4 * (eh)**2) mod p", tmp) + + /* tmp = tmp - eh mod p */ + if (!BN_mod_sub(tmp, tmp, m_p, rw->P, ctx)) + goto err; + + /* f = 1 if zero else 2 */ + f = 2 - BN_is_zero(tmp); + /* f = 2 - (constant_time_is_zero(BN_ucmp(tmp, m_p)) & 1); */ + DEBUG_PRINTF("f == %i\n", f) + +#ifdef AVOID_IF + if (!BN_mod_mul(tmp_U, U, rw->twomq, rw->q, ctx)) + goto err; + + /* + to avoid "if" branch we use tiny additional computation + */ + U = (BIGNUM*)((2 - f) * (BN_ULONG)U + (1 - (2 - f)) * (BN_ULONG)tmp_U); +#else + + if (f == 2) { + if (!BN_mod_mul(U, U, rw->Twomq, rw->Q, ctx)) + goto err; + } + +#endif + + DEBUG_PRINT_BN("W", U) + + /* V ** 3 */ + if (!BN_mod_sqr(tmp, V, rw->P, ctx)) + goto err; + if (!BN_mod_mul(V, V, tmp, rw->P, ctx)) + goto err; + DEBUG_PRINT_BN("V**3", V) + + /* *(eh) */ + if (!BN_mod_mul(V, V, m_p, rw->P, ctx)) + goto err; + DEBUG_PRINT_BN("V**3 * (eh) mod p", V) + +#ifdef AVOID_IF + + /* to avoid "if" statement we use simple computation */ + if (!BN_mod_mul(tmp_V, V, rw->twomp, rw->p, ctx)) + goto err; + V = (BIGNUM*)((2 - f) * (BN_ULONG)V + (1 - (2 - f)) * (BN_ULONG)tmp_V); + +#else + + if (f == 2) { + if (!BN_mod_mul(V, V, rw->Twomp, rw->P, ctx)) + goto err; + } + +#endif + + DEBUG_PRINT_BN("X", V) + + /* W = U, X = V */ + if (!BN_mod_sub(V, V, U, rw->P, ctx)) + goto err; + DEBUG_PRINT_BN("X - W mod p", V) + + if (!BN_mod_mul(V, V, rw->Iqmp, rw->P, ctx)) + goto err; + DEBUG_PRINT_BN("q**(p-2) * (X-W) mod p", V) + + if (!BN_mul(V, V, rw->Q, ctx)) + goto err; + DEBUG_PRINT_BN("q * prev mod p", V) + + if (!BN_mod_add(V, U, V, rw->N, ctx)) + goto err; + DEBUG_PRINT_BN("Y", V) + + /* now V = Y */ + if (!BN_mod_sqr(V, V, rw->N, ctx)) + goto err; + DEBUG_PRINT_BN("s", V) + +#ifdef FAULT_TOLERANCE_CHECK + + /* now V = s - principal square root */ + /* fault tolerance check */ + if (!BN_mod_sqr(tmp, V, rw->n, ctx)) + goto err; + DEBUG_PRINT_BN("s**2", tmp) + + if (!BN_mul_word(tmp, f)) + goto err; + DEBUG_PRINT_BN("f * s**2", tmp) + + if (!BN_nnmod(tmp, tmp, rw->n, ctx)) + goto err; + DEBUG_PRINT_BN("s**2 * f mod n", tmp) + + /* to avoid "if" statement */ + if (!BN_sub(tmp2, rw->n, tmp)) + goto err; + tmp = (BIGNUM*)(((1 + e) >> 1) * (BN_ULONG)tmp + (1 - ((1 + e) >> 1)) * (BN_ULONG)tmp2); + DEBUG_PRINT_BN("ef(s**2)", tmp) + DEBUG_PRINT_BN("(tmp == original m)", tmp) + + if (BN_ucmp(tmp, m) != 0) + goto err; + +#endif + + /* making the "principal square root" to be "|principal| square root" */ + if (!BN_sub(tmp, rw->N, V)) + goto err; + + /* if tmp = MIN(V, rw->n - V) */ + tmp = BN_ucmp(tmp, V) >= 0 ? V : tmp; + + if (!BN_copy(ret->S, tmp)) + goto err; + + ok = 1; + +err: + if (ctx != NULL) { + BN_CTX_end(ctx); + BN_CTX_free(ctx); + } + if (!ok) { + RwSignatureFree(ret); + ret = NULL; + } + + return ret; +} + +static int RwDoVerify(const unsigned char* dgst, int dgst_len, TRwSignature* sig, const TRwKey* rw) { + BIGNUM *m = NULL, *x = NULL, *t1 = NULL, *t2 = NULL, *t1d = NULL, *t2d = NULL; + BN_CTX* ctx = NULL; + BN_ULONG rest1 = 0, rest2 = 0; + int retval = 0; + + if (!rw || !rw->N || !sig || !sig->S) + goto err; + + if ((ctx = BN_CTX_secure_new()) == NULL) + goto err; + BN_CTX_start(ctx); + + m = BN_CTX_get(ctx); + t1 = BN_CTX_get(ctx); + t2 = BN_CTX_get(ctx); + t1d = BN_CTX_get(ctx); + t2d = BN_CTX_get(ctx); + + if (!BN_bin2bn(dgst, dgst_len, m)) + goto err; + /* dgst too big */ + if (!BN_copy(t1, rw->N)) + goto err; + if (!BN_sub_word(t1, 1)) + goto err; + if (!BN_rshift(t1, t1, 1)) + goto err; + + /* check m and rw->n relation */ + if (BN_ucmp(m, rw->N) >= 0) + goto err; + rest1 = BN_mod_word(m, 16); + if (rest1 != 12) + goto err; + + if (BN_ucmp(t1, sig->S) < 0) + goto err; + if (BN_is_negative(sig->S)) + goto err; + + if (!BN_mod_sqr(t1, sig->S, rw->N, ctx)) + goto err; + if (!BN_sub(t2, rw->N, t1)) + goto err; + if (!BN_lshift1(t1d, t1)) + goto err; + if (!BN_lshift1(t2d, t2)) + goto err; + + rest1 = BN_mod_word(t1, 16); + rest2 = BN_mod_word(t2, 16); + + /* mod 16 */ + if (rest1 == 12) { + x = t1; + } + /* mod 8 */ + else if ((rest1 & 0x07) == 6) { + x = t1d; + } + /* mod 16 */ + else if (rest2 == 12) { + x = t2; + } + /* mod 8 */ + else if ((rest2 & 0x07) == 6) { + x = t2d; + } else + goto err; + + DEBUG_PRINT_BN("m", m) + DEBUG_PRINT_BN("x", x) + + /* check signature value */ + retval = BN_ucmp(m, x) == 0; + +err: + if (ctx != NULL) { + BN_CTX_end(ctx); + BN_CTX_free(ctx); + } + return retval; +} + +static int RwDoApply(BIGNUM* r, BIGNUM* x, BN_CTX* ctx, const TRwKey* rw) { + BIGNUM *t1 = NULL, *t2 = NULL, *t1d = NULL, *t2d = NULL, *rs = NULL; + BN_ULONG rest1 = 0, rest2 = 0; + int retval = 0; + + if (!rw || !rw->N || !x || !ctx || !r) + goto err; + + DEBUG_PRINT_BN("Signature = x = ", x) + DEBUG_PRINT_BN("n", rw->n) + + BN_CTX_start(ctx); + + t1 = BN_CTX_get(ctx); + t2 = BN_CTX_get(ctx); + t1d = BN_CTX_get(ctx); + t2d = BN_CTX_get(ctx); + + if (!BN_copy(t1, rw->N)) + goto err; + if (!BN_sub_word(t1, 1)) + goto err; + if (!BN_rshift(t1, t1, 1)) + goto err; + + /* check m and rw->n relation */ + if (BN_ucmp(x, rw->N) >= 0) + goto err; + + if (BN_ucmp(t1, x) < 0) + goto err; + if (BN_is_negative(x)) + goto err; + + if (!BN_mod_sqr(t1, x, rw->N, ctx)) + goto err; + DEBUG_PRINT_BN("x**2 mod n", t1) + + if (!BN_sub(t2, rw->N, t1)) + goto err; + DEBUG_PRINT_BN("n - x**2", t2) + + if (!BN_lshift1(t1d, t1)) + goto err; + if (!BN_lshift1(t2d, t2)) + goto err; + + rest1 = BN_mod_word(t1, 16); + rest2 = BN_mod_word(t2, 16); + + /* mod 16 */ + if (rest1 == 12) { + rs = t1; + } + /* mod 8 */ + else if ((rest1 & 0x07) == 6) { + rs = t1d; + } + /* mod 16 */ + else if (rest2 == 12) { + rs = t2; + } + /* mod 8 */ + else if ((rest2 & 0x07) == 6) { + rs = t2d; + } else + goto err; + + DEBUG_PRINT_BN("Squaring and shifting result (rs)", rs) + retval = BN_copy(r, rs) != NULL; + +err: + BN_CTX_end(ctx); + return retval; +} diff --git a/library/cpp/tvmauth/src/rw/rw_pss.c b/library/cpp/tvmauth/src/rw/rw_pss.c new file mode 100644 index 0000000000..1e040a55eb --- /dev/null +++ b/library/cpp/tvmauth/src/rw/rw_pss.c @@ -0,0 +1,328 @@ +/* + * This code was taken from the OpenSSL's RSA implementation + * and added to the RW project with some changes + * + * Written by Dr Stephen N Henson (steve@openssl.org) for the OpenSSL + * project 2005. + * + */ +/* ==================================================================== + * Copyright (c) 2005 The OpenSSL Project. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. All advertising materials mentioning features or use of this + * software must display the following acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit. (http://www.OpenSSL.org/)" + * + * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please contact + * licensing@OpenSSL.org. + * + * 5. Products derived from this software may not be called "OpenSSL" + * nor may "OpenSSL" appear in their names without prior written + * permission of the OpenSSL Project. + * + * 6. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit (http://www.OpenSSL.org/)" + * + * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY + * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * ==================================================================== + * + * This product includes cryptographic software written by Eric Young + * (eay@cryptsoft.com). This product includes software written by Tim + * Hudson (tjh@cryptsoft.com). + * + */ + +#include "rw.h" + +#include <openssl/bn.h> +#include <openssl/evp.h> +#include <openssl/rand.h> +#include <openssl/sha.h> + +#include <stdio.h> +#include <string.h> + +static const unsigned char zeroes[] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + +static int PkcS1MgF1(unsigned char *mask, const int len, const unsigned char *seed, const int seedlen, const EVP_MD *dgst) { + int i, outlen = 0; + unsigned char cnt[4]; + EVP_MD_CTX* c = EVP_MD_CTX_create(); + unsigned char md[EVP_MAX_MD_SIZE]; + int mdlen; + int rv = -1; + + if (!c) { + return rv; + } + + mdlen = EVP_MD_size(dgst); + + if (mdlen < 0 || seedlen < 0) + goto err; + + for (i = 0; outlen < len; i++) { + cnt[0] = (unsigned char)((i >> 24) & 255); + cnt[1] = (unsigned char)((i >> 16) & 255); + cnt[2] = (unsigned char)((i >> 8)) & 255; + cnt[3] = (unsigned char)(i & 255); + + if (!EVP_DigestInit_ex(c,dgst, NULL) || !EVP_DigestUpdate(c, seed, seedlen) || !EVP_DigestUpdate(c, cnt, 4)) + goto err; + + if (outlen + mdlen <= len) { + if (!EVP_DigestFinal_ex(c, mask + outlen, NULL)) + goto err; + outlen += mdlen; + } else { + if (!EVP_DigestFinal_ex(c, md, NULL)) + goto err; + memcpy(mask + outlen, md, len - outlen); + outlen = len; + } + } + rv = 0; + +err: + EVP_MD_CTX_destroy(c); + return rv; +} + +int RwVerifyPssr(const TRwKey *rw, const unsigned char *mHash, const EVP_MD *Hash, const unsigned char *EM, int sLen) { + int i = 0, ret = 0, hLen = 0, maskedDBLen = 0, MSBits = 0, emLen = 0; + const unsigned char *H = NULL; + unsigned char *DB = NULL; + EVP_MD_CTX* ctx = NULL; + unsigned char H_[EVP_MAX_MD_SIZE]; + const EVP_MD *mgf1Hash = Hash; + + ctx = EVP_MD_CTX_create(); + if (!ctx) { + return ret; + } + hLen = EVP_MD_size(Hash); + + if (hLen < 0) + goto err; + /* + * Negative sLen has special meanings: + * -1 sLen == hLen + * -2 salt length is autorecovered from signature + * -N reserved + */ + if (sLen == -1) + sLen = hLen; + else if (sLen < -2) + goto err; + + { + int bits = BN_num_bits(rw->N); + if (bits <= 0) + goto err; + + MSBits = (bits - 1) & 0x7; + } + emLen = RwModSize(rw); + + if (EM[0] & (0xFF << MSBits)) { + goto err; + } + + if (MSBits == 0) { + EM++; + emLen--; + } + + if (emLen < (hLen + sLen + 2)) /* sLen can be small negative */ + goto err; + + if (emLen < 1) + goto err; + + if (EM[emLen - 1] != 0xbc) + goto err; + + maskedDBLen = emLen - hLen - 1; + if (maskedDBLen <= 0) + goto err; + + H = EM + maskedDBLen; + DB = malloc(maskedDBLen); + + if (!DB) + goto err; + + if (PkcS1MgF1(DB, maskedDBLen, H, hLen, mgf1Hash) < 0) + goto err; + + for (i = 0; i < maskedDBLen; i++) + DB[i] ^= EM[i]; + + if (MSBits) + DB[0] &= 0xFF >> (8 - MSBits); + + for (i = 0; DB[i] == 0 && i < (maskedDBLen-1); i++) ; + + if (DB[i++] != 0x1) + goto err; + + if (sLen >= 0 && (maskedDBLen - i) != sLen) + goto err; + + if (!EVP_DigestInit_ex(ctx, Hash, NULL) || !EVP_DigestUpdate(ctx, zeroes, sizeof zeroes) || !EVP_DigestUpdate(ctx, mHash, hLen)) + goto err; + + if (maskedDBLen - i) { + if (!EVP_DigestUpdate(ctx, DB + i, maskedDBLen - i)) + goto err; + } + + if (!EVP_DigestFinal_ex(ctx, H_, NULL)) + goto err; + + ret = memcmp(H, H_, hLen) ? 0 : 1; + +err: + if (DB) + free(DB); + + EVP_MD_CTX_destroy(ctx); + + return ret; +} + +/* + rw - public key + EM - buffer to write padding value + mHash - hash value + Hash - EVP_MD() that will be used to pad + sLen - random salt len (usually == hashLen) + */ +int RwPaddingAddPssr(const TRwKey *rw, unsigned char *EM, const unsigned char *mHash, const EVP_MD *Hash, int sLen) { + int i = 0, ret = 0, hLen = 0, maskedDBLen = 0, MSBits = 0, emLen = 0; + unsigned char *H = NULL, *salt = NULL, *p = NULL; + const EVP_MD *mgf1Hash = Hash; + EVP_MD_CTX* ctx = EVP_MD_CTX_create(); + if (!ctx) { + return ret; + } + + hLen = EVP_MD_size(Hash); + if (hLen < 0) + goto err; + /* + * Negative sLen has special meanings: + * -1 sLen == hLen + * -2 salt length is maximized + * -N reserved + */ + if (sLen == -1) + sLen = hLen; + else if (sLen < -2) + goto err; + + { + int bits = BN_num_bits(rw->N); + if (bits <= 0) + goto err; + MSBits = (bits - 1) & 0x7; + } + emLen = RwModSize(rw); + if (emLen <= 0) + goto err; + + if (MSBits == 0) { + *EM++ = 0; + emLen--; + fprintf(stderr, "MSBits == 0\n"); + } + + if (sLen == -2) { + sLen = emLen - hLen - 2; + } + else if (emLen < (hLen + sLen + 2)) + goto err; + + if (sLen > 0) { + salt = malloc(sLen); + if (!salt) goto err; + if (RAND_bytes(salt, sLen) <= 0) + goto err; + } + + maskedDBLen = emLen - hLen - 1; + if (maskedDBLen < 0) + goto err; + H = EM + maskedDBLen; + + if (!EVP_DigestInit_ex(ctx, Hash, NULL) || !EVP_DigestUpdate(ctx, zeroes, sizeof zeroes) || !EVP_DigestUpdate(ctx, mHash, hLen)) + goto err; + + if (sLen && !EVP_DigestUpdate(ctx, salt, sLen)) + goto err; + + if (!EVP_DigestFinal_ex(ctx, H, NULL)) + goto err; + + /* Generate dbMask in place then perform XOR on it */ + if (PkcS1MgF1(EM, maskedDBLen, H, hLen, mgf1Hash)) + goto err; + + p = EM; + + /* Initial PS XORs with all zeroes which is a NOP so just update + * pointer. Note from a test above this value is guaranteed to + * be non-negative. + */ + p += emLen - sLen - hLen - 2; + *p++ ^= 0x1; + + if (sLen > 0) { + for (i = 0; i < sLen; i++) + *p++ ^= salt[i]; + } + + if (MSBits) + EM[0] &= 0xFF >> (8 - MSBits); + + /* H is already in place so just set final 0xbc */ + EM[emLen - 1] = 0xbc; + + ret = 1; + +err: + EVP_MD_CTX_destroy(ctx); + + if (salt) + free(salt); + + return ret; +} diff --git a/library/cpp/tvmauth/src/rw/rw_pss_sign.c b/library/cpp/tvmauth/src/rw/rw_pss_sign.c new file mode 100644 index 0000000000..683514ad6d --- /dev/null +++ b/library/cpp/tvmauth/src/rw/rw_pss_sign.c @@ -0,0 +1,211 @@ +#include "rw.h" + +#include <openssl/evp.h> + +//#define DBG_FUZZING + +int RwApply(const int flen, const unsigned char* from, unsigned char* to, const TRwKey* rw) { + int i, j, num, k, r = -1; + BN_CTX* ctx = NULL; + BIGNUM *f = NULL, *ret = NULL; + + if ((ctx = BN_CTX_secure_new()) == NULL) + goto err; + BN_CTX_start(ctx); + + f = BN_CTX_get(ctx); + ret = BN_CTX_get(ctx); + + num = BN_num_bytes(rw->N); + + if (num <= 0) + goto err; + + if (!f || !ret) + goto err; + + if (BN_bin2bn(from, flen, f) == NULL) + goto err; + if (BN_ucmp(f, rw->N) >= 0) + goto err; + + if (!rw->Meth->RwApply(ret, f, ctx, rw)) + goto err; + + j = BN_num_bytes(ret); + if (num < j || j < 0) + goto err; + + i = BN_bn2bin(ret, to + num - j); + if (i < 0 || i > num) + goto err; + + for (k = 0; k < (num - i); k++) + to[k] = 0; + r = num; + +err: + if (ctx != NULL) { + BN_CTX_end(ctx); + BN_CTX_free(ctx); + } + return r; +} + +int RwPssrSignHash(const unsigned char* from, unsigned char* to, TRwKey* rw, const EVP_MD* md) { + unsigned char* padding = NULL; + int result = 0; + + if (from == NULL || to == NULL || rw == NULL || md == NULL) + return 0; + + int digest_size = EVP_MD_size(md); + int sig_size = RwModSize(rw); + + if (digest_size <= 0 || sig_size <= 0) + return 0; + + int tries = 50; + do { + if (padding != NULL) { + free(padding); +#ifdef DBG_FUZZING + fprintf(stderr, "Padding regenerating required\n"); +#endif + } + + padding = malloc(sig_size); + if (padding == NULL) + return 0; + + if (!RwPaddingAddPssr(rw, padding, from, md, digest_size)) + goto err; + } while (padding[0] == 0x00 && tries-- > 0); + + result = RwNoPaddingSign(sig_size, padding, to, rw); + +err: + if (padding != NULL) + free(padding); + + return result; +} + +int RwPssrSignMsg(const int msgLen, const unsigned char* msg, unsigned char* to, TRwKey* rw, const EVP_MD* md) { + EVP_MD_CTX* mdctx = NULL; + unsigned char* digest = NULL; + unsigned int digestLen; + int result = 0; + + if (msg == NULL || to == NULL || rw == NULL || md == NULL) + goto err; + + if (rw->P == NULL || rw->Q == NULL) + goto err; + + if ((mdctx = EVP_MD_CTX_create()) == NULL) + goto err; + + if (1 != EVP_DigestInit_ex(mdctx, md, NULL)) + goto err; + + if (1 != EVP_DigestUpdate(mdctx, msg, msgLen)) + goto err; + + if ((digest = (unsigned char*)malloc(EVP_MD_size(md))) == NULL) + goto err; + + if (1 != EVP_DigestFinal_ex(mdctx, digest, &digestLen)) + goto err; + + result = RwPssrSignHash(digest, to, rw, md); + +err: + if (mdctx != NULL) + EVP_MD_CTX_destroy(mdctx); + if (digest != NULL) + free(digest); + + return result; +} + +int RwPssrVerifyHash(const unsigned char* from, const unsigned char* sig, const int sig_len, const TRwKey* rw, const EVP_MD* md) { + unsigned char* buffer = NULL; + int buffer_len; + int salt_size; + int result = 0; + + if (from == NULL || sig == NULL || rw == NULL || md == NULL) + return 0; + + if (rw->N == NULL || rw->Meth == NULL) + return 0; + + salt_size = EVP_MD_size(md); + if (salt_size <= 0) + return 0; + + buffer_len = RwModSize(rw); + if (buffer_len <= 0) + return 0; + + buffer = (unsigned char*)malloc(buffer_len); + if (buffer == NULL) + return 0; + + if (RwApply(sig_len, sig, buffer, rw) <= 0) + goto err; + + if (RwVerifyPssr(rw, from, md, buffer, salt_size) <= 0) + goto err; + + result = 1; + +err: + if (buffer != NULL) + free(buffer); + + return result; +} + +int RwPssrVerifyMsg(const int msgLen, const unsigned char* msg, const unsigned char* sig, const int sig_len, const TRwKey* rw, const EVP_MD* md) { + EVP_MD_CTX* mdctx = NULL; + unsigned char* digest = NULL; + unsigned int digestLen = 0; + int result = 0; + + if (msg == NULL || msgLen == 0 || sig == NULL || rw == NULL || md == NULL) + goto err; + + if (rw->N == NULL) + goto err; + + if ((mdctx = EVP_MD_CTX_create()) == NULL) + goto err; + + if (1 != EVP_DigestInit_ex(mdctx, md, NULL)) + goto err; + + int size_to_alloc = EVP_MD_size(md); + if (size_to_alloc <= 0) + goto err; + + if ((digest = (unsigned char*)malloc(size_to_alloc)) == NULL) + goto err; + + if (1 != EVP_DigestUpdate(mdctx, msg, msgLen)) + goto err; + + if (1 != EVP_DigestFinal_ex(mdctx, digest, &digestLen)) + goto err; + + result = RwPssrVerifyHash(digest, sig, sig_len, rw, md); + +err: + if (mdctx != NULL) + EVP_MD_CTX_destroy(mdctx); + if (digest != NULL) + free(digest); + + return result; +} diff --git a/library/cpp/tvmauth/src/rw/rw_sign.c b/library/cpp/tvmauth/src/rw/rw_sign.c new file mode 100644 index 0000000000..e320808dd3 --- /dev/null +++ b/library/cpp/tvmauth/src/rw/rw_sign.c @@ -0,0 +1,46 @@ +#include "rw.h" + +TRwSignature* RwSignatureNew(void) { + TRwSignature* sig = NULL; + sig = malloc(sizeof(TRwSignature)); + if (!sig) + return NULL; + sig->S = NULL; + return sig; +} + +void RwSignatureFree(TRwSignature* sig) { + if (sig) { + if (sig->S) + BN_free(sig->S); + free(sig); + } +} + +int RwNoPaddingSign(int flen, const unsigned char* from, unsigned char* to, TRwKey* rw) { + int i = 0, r = 0, num = -1; + TRwSignature* sig = NULL; + + if (!rw || !rw->N || !rw->Meth || !rw->Meth->RwSign || !from || !to) + goto err; + + if ((sig = rw->Meth->RwSign(from, flen, rw)) == NULL) + goto err; + num = BN_num_bytes(rw->N); + + r = BN_bn2bin(sig->S, to); + if (r < 0) + goto err; + + /* put zeroes to the rest of the 'to' buffer */ + for (i = r; i < num; i++) { + to[i] = 0x00; + } + +err: + if (sig != NULL) { + RwSignatureFree(sig); + } + + return r; +} diff --git a/library/cpp/tvmauth/src/rw/ut/rw_ut.cpp b/library/cpp/tvmauth/src/rw/ut/rw_ut.cpp new file mode 100644 index 0000000000..9467d5d7c4 --- /dev/null +++ b/library/cpp/tvmauth/src/rw/ut/rw_ut.cpp @@ -0,0 +1,200 @@ +#include <library/cpp/tvmauth/src/rw/keys.h> +#include <library/cpp/tvmauth/src/rw/rw.h> + +#include <library/cpp/string_utils/base64/base64.h> +#include <library/cpp/testing/unittest/registar.h> + +#include <openssl/bn.h> +#include <openssl/evp.h> + +namespace NTvmAuth { + /* + returns 0 in case of error + */ + int MakeKeysRw(TRwKey** skey, TRwKey** vkey) { + int result = 0; + + TRwKey* rw = RwNew(); + + do { + RwGenerateKey(rw, 2048); + + if (rw == nullptr) { + printf("RwGenerateKey failed\n"); + break; /* failed */ + } + + printf("RW key bits: %d\n", BN_num_bits(rw->N)); + + /* Set signing key */ + *skey = RwPrivateKeyDup(rw); + if (*skey == nullptr) { + printf("RwPrivateKeyDup failed\n"); + break; + } + + /* Set verifier key */ + *vkey = RwPublicKeyDup(rw); + if (*vkey == nullptr) { + printf("RwPublicKeyDup failed\n"); + break; + } + + result = 1; + + } while (0); + + if (rw) { + RwFree(rw); + rw = nullptr; + } + + return result; + } + + static void PrintIt(const char* label, const unsigned char* buff, size_t len) { + if (!buff || !len) + return; + + if (label) + printf("%s: ", label); + + for (size_t i = 0; i < len; ++i) + printf("%02X", buff[i]); + + printf("\n"); + } + + int TestSignVerify() { + TRwKey *skey = nullptr, *vkey = nullptr; + const char* msg = "Test test test test test"; + unsigned int msg_len = 0; + int res = 0; + + msg_len = (unsigned int)strlen(msg); + if (MakeKeysRw(&skey, &vkey)) { + unsigned char* sign = new unsigned char[RwModSize(skey) + 10]; + int sign_len; + printf("RwModSize(skey) returned %d\n", RwModSize(skey)); + memset(sign, 0x00, RwModSize(skey) + 10); + + printf("--- Signing call ---\n"); + if ((sign_len = RwPssrSignMsg(msg_len, (unsigned char*)msg, sign, skey, (EVP_MD*)EVP_sha256())) != 0) { +#ifdef RW_PRINT_DEBUG + BIGNUM* s = BN_new(); +#endif + printf("\n"); + PrintIt("Signature", sign, RwModSize(skey)); + +#ifdef RW_PRINT_DEBUG + BN_bin2bn(sign, RW_mod_size(skey), s); + + print_bn("Signature BN", s); + + BN_free(s); +#endif + + printf("--- Verification call ---\n"); + if (RwPssrVerifyMsg(msg_len, (unsigned char*)msg, sign, sign_len, vkey, (EVP_MD*)EVP_sha256())) { + printf("Verification: success!\n"); + res = 1; + } else { + printf("Verification: failed!\n"); + printf("RwPssrVerifyMsg failed!\n"); + return 1; + } + } else { + printf("RwPssrSignMsg failed!\n"); + return 1; + } + + if (sign != nullptr) + delete[] sign; + + } else { + printf("MakeKeysRw failed!\n"); + return 1; + } + + if (skey != nullptr) { + RwFree(skey); + } + if (vkey != nullptr) + RwFree(vkey); + + return res; + } +} + +using namespace NTvmAuth; +Y_UNIT_TEST_SUITE(Rw) { + Y_UNIT_TEST(SignVerify) { + for (int i = 1; i < 10; ++i) { + UNIT_ASSERT_VALUES_EQUAL(1, TestSignVerify()); + } + } + + Y_UNIT_TEST(TKeysPriv) { + NRw::TRwPrivateKey priv(Base64Decode("MIIEmwKCAQBwsRd4frsVARIVSfj_vCdfvA3Q9SsGhSybdBDhbm8L6rPqxdoSNLCdNXzDWj7Ppf0o8uWHMxC-5Lfw0I18ri68nhm9-ndixcnbn6ti1uetgkc28eiEP6Q8ILD_JmkynbUl1aKDNAa5XsK2vFSEX402uydRomsTn46kRY23hfqcIi0ohh5VxIrpclRsRZus0JFu-RJzhqTbKYV4y4dglWPGHh5BuTv9k_Oh0_Ra8Xp5Rith5vjaKZUQ5Hyh9UtBYTkNWdvXP9OpmbiLVeRLuMzBm4HEFHDwMZ1h6LSVP-wB_spJPaMLTn3Q3JIHe-wGBYRWzU51RRYDqv4O_H12w5C1AoGBALAwCQ7fdAPG1lGclL7iWFjUofwPCFwPyDjicDT_MRRu6_Ta4GjqOGO9zuOp0o_ePgvR-7nA0fbaspM4LZNrPZwmoYBCJMtKXetg68ylu2DO-RRSN2SSh1AIZSA_8UTABk69bPzNL31j4PyZWxrgZ3zP9uZvzggveuKt5ZhCMoB7AoGBAKO9oC2AZjLdh2RaEFotTL_dY6lVcm38VA6PnigB8gB_TMuSrd4xtRw5BxvHpOCnBcUAJE0dN4_DDe5mrotKYMD2_3_lcq9PaLZadrPDCSDL89wtoVxNQNAJTqFjBFXYNu4Ze63lrsqg45TF5XmVRemyBHzXw3erd0pJaeoUDaSPAoGAJhGoHx_nVw8sDoLzeRkOJ1_6-uh_wVmVr6407_LPjrrySEq-GiYu43M3-QDp8J_J9e3S1Rpm4nQX2bEf5Gx9n4wKz7Hp0cwkOqBOWhvrAu6YLpv59wslEtkx0LYcJy6yQk5mpU8l29rPO7b50NyLnfnE2za-9DyK038FKlr5VgICgYAUd7QFsAzGW7Dsi0ILRamX-6x1Kq5Nv4qB0fPFAD5AD-mZclW7xjajhyDjePScFOC4oASJo6bx-GG9zNXRaUwYHt_v_K5V6e0Wy07WeGEkGX57hbQriagaASnULGCKuwbdwy91vLXZVBxymLyvMqi9NkCPmvhu9W7pSS09QoG0kgKBgBYGASHb7oB42sozkpfcSwsalD-B4QuB-QccTgaf5iKN3X6bXA0dRwx3udx1OlH7x8F6P3c4Gj7bVlJnBbJtZ7OE1DAIRJlpS71sHXmUt2wZ3yKKRuySUOoBDKQH_iiYAMnXrZ-Zpe-sfB-TK2NcDO-Z_tzN-cEF71xVvLMIRlAPAoGAdeikZPh1O57RxnVY72asiMRZheMBhK-9uSNPyYEZv3bUnIjg4XdMYStF2yTHNu014XvkDSQTe-drv2BDs9ExKplM4xFOtDtPQQ3mMB3GoK1qVhM_9n1QEElreurMicahkalnPo6tU4Z6PFL7PTpjRnCN67lJp0J0fxNDL13YSagCgYBA9VJrMtPjzcAx5ZCIYJjrYUPqEG_ttQN2RJIHN3MVpdpLAMIgX3tnlfyLwQFVKK45D1JgFa_1HHcxTWGtdIX4nsIjPWt-cWCCCkkw9rM5_Iqcb-YLSood6IP2OK0w0XLD1STnFRy_BRwdjPbGOYmp6YrJDZAlajDkFSdRvsz9Vg=="), + 0); + NRw::TRwPrivateKey priv2(Base64Decode("MIIEnAKCAQEA4RATOfumLD1n6ICrW5biaAl9VldinczmkNPjpUWwc3gs8PnkCrtdnPFmpBwW3gjHdSNU1OuEg5A6K1o1xiGv9sU-jd88zQBOdK6E2zwnJnkK6bNusKE2H2CLqg3aMWCmTa9JbzSy1uO7wa-xCqqNUuCko-2lyv12HhL1ICIH951SHDa4qO1U5xZhhlUAnqWi9R4tYDeMiF41WdOjwT2fg8UkbusThmxa3yjCXjD7OyjshPtukN8Tl3UyGtV_s2CLnE3f28VAi-AVW8FtgL22xbGhuyEplXRrtF1E5oV7NSqxH1FS0SYROA8ffYQGV5tfx5WDFHiXDEP6BzoVfeBDRQKBgQDzidelKZNFMWar_yj-r_cniMkZXNaNVEQbMg1A401blGjkU1r-ufGH5mkdNx4IgEoCEYBTM834Z88fYV1lOVfdT0OqtiVoC9NkLu3xhQ1r9_r6RMaAenwsV7leH8jWMOKvhkB0KNI49oznTGDqLp0AbDbtP66xdNH4dr3rw3WFywKBgQDslDdv4sdnRKN27h2drhn4Pp_Lgw2U-6MfHiyjp6BKR8Qtlld3hdb-ZjU9F0h38DqECmFIEe35_flKfd7X21CBQs9EuKR8EdaF3OAgzA-TRWeQhyHmaV7Fas1RlNqZHm8lckaZT8dX9Ygsxn0I_vUbm9pkFivwGvQnnwNQ7Te5LwKBgCVMYOzLHW911l6EbCZE6XU2HUrTKEd1bdqWCgtxPEmDl3BZcXpnyKpqSHmlH1F7s65WBfejxDM2hjin3OnXSog_x35ql_-Azu93-79QAzbQc6Z13BuWPpQxV8iw4ijqRRhzjD2pcvXlIxgebp5-H0eDt-Md2Y8rkrzyhm8EH7mwAoGAHZKG7fxY7OiUbt3Ds7XDPwfT-XBhsp90Y-PFlHT0CUj4hbLK7vC638zGp6LpDv4HUIFMKQI9vz-_KU-72vtqEChZ6JcUj4I60LucBBmB8mis8hDkPM0r2K1ZqjKbUyPN5K5I0yn46v6xBZjPoR_eo3N7TILFfgNehPPgah2m9yYCgYAecTr0pTJopizVf-Uf1f7k8RkjK5rRqoiDZkGoHGmrco0cimtf1z4w_M0jpuPBEAlAQjAKZnm_DPnj7Cuspyr7qeh1VsStAXpshd2-MKGtfv9fSJjQD0-Fivcrw_kaxhxV8MgOhRpHHtGc6YwdRdOgDYbdp_XWLpo_Dte9eG6wuQKBgDzo0e8d8pTyvCP23825rVzvrSHBZkliGkCEu0iggDnfKOreejFhQN9JeBo8sYdQFCRBptEU6k4b5O6J3NQ1Sspiez15ddqmFMD4uhJY6VsV-JFnL9YhLqVd355xZCyU4b07mReU9-LuqK2m2chjxH_HDAgUoEvO_yzR9EDYqHbNAoGAf529Ah9HIT5aG6IGTlwQdk-M7guy63U4vj4uC7z98qgvFEsV6cr4miT6RE8Aw5yAeN5pW59rZNjBNr9i-8n8kouasho2xNMTPKP8YuSNg2PNNS5T1Ou56mgsBCY5i10TIHKNIm2RVSUgzJ97BMEOZY6jQRytFfwgYkvnFzbuA9c="), + 0); + NRw::TRwPrivateKey priv3(Base64Decode("MIICVAKBgF9t2YJGAJkRRFq6fWhi3m1TFW1UOE0f6ZrfYhHAkpqGlKlh0QVfeTNPpeJhi75xXzCe6oReRUm-0DbqDNhTShC7uGUv1INYnRBQWH6E-5Fc5XrbDFSuGQw2EYjNfHy_HefHJXxQKAqPvxBDKMKkHgV58WtM6rC8jRi9sdX_ig2NAkEAg1xBDL_UkHy347HwioMscJFP-6eKeim3LoG9rd1EvOycxkoStZ4299OdyzzEXC9cjLdq401BXe-LairiMUgZawJBALn5ziBCc2ycMaYjZDon2EN55jBEe0tJdUy4mOi0ozTV9OLcBANds0nMYPjZFOY3QymzU0LcOa_An3JknI0C2ucCQGxtwTb3h7ux5Ld8jkeRYzkNoB2Y6Is5fqCYVRIJZmz0IcQFb2iW0EX92U7_BpgVuKlvSDTP9LuaxuPfmY6WXEECQBc_OcQITm2ThjTEbIdE-whvPMYIj2lpLqmXEx0WlGaavpxbgIBrtmk5jB8bIpzG6GU2amhbhzX4E-5Mk5GgW10CQBBriCGX-pIPlvx2PhFQZY4SKf908U9FNuXQN7W7qJedk5jJQlazxt76c7lnmIuF65GW7VxpqCu98W1FXEYpAy0CQG-lpihdvxaZ8SkHqNFZGnXhELT2YesLs7GehZSTwuUwx1iTpVm88PVROLYBDZqoGM316s9aZEJBALe5zEpxQTQCQQCDMszX1cQlbBCP08isuMQ2ac3S-qNd0mfRXDCRfMm4s7iuJ5MeHU3uPUVlA_MR4ULRbg1d97TGio912z4KPgjE"), + 0); + + UNIT_ASSERT_EXCEPTION(NRw::TRwPrivateKey("asdzxcv", 0), yexception); + UNIT_ASSERT_EXCEPTION(NRw::TRwPrivateKey(Base64Decode("AKBgF9t2YJGAJkRRFq6fWhi3m1TFW1UOE0f6ZrfYhHAkpqGlKlh0QVfeTNPpeJhi75xXzCe6oReRUm-0DbqDNhTShC7uGUv1INYnRBQWH6E-5Fc5XrbDFSuGQw2EYjNfHy_HefHJXxQKAqPvxBDKMKkHgV58WtM6rC8jRi9sdX_ig2NAkEAg1xBDL_UkHy347HwioMscJFP-6eKeim3LoG9rd1EvOycxkoStZ4299OdyzzEXC9cjLdq401BXe-LairiMUgZawJBALn5ziBCc2ycMaYjZDon2EN55jBEe0tJdUy4mOi0ozTV9OLcBANds0nMYPjZFOY3QymzU0LcOa_An3JknI0C2ucCQGxtwTb3h7ux5Ld8jkeRYzkNoB2Y6Is5fqCYVRIJZmz0IcQFb2iW0EX92U7_BpgVuKlvSDTP9LuaxuPfmY6WXEECQBc_OcQITm2ThjTEbIdE-whvPMYIj2lpLqmXEx0WlGaavpxbgIBrtmk5jB8bIpzG6GU2amhbhzX4E-5Mk5GgW10CQBBriCGX-pIPlvx2PhFQZY4SKf908U9FNuXQN7W7qJedk5jJQlazxt76c7lnmIuF65GW7VxpqCu98W1FXEYpAy0CQG-lpihdvxaZ8SkHqNFZGnXhELT2YesLs7GehZSTwuUwx1iTpVm88PVROLYBDZqoGM316s9aZEJBALe5zEpxQTQCQQCDMszX1cQlbBCP08isuMQ2ac3S-qNd0mfRXDCRfMm4s7iuJ5MeHU3uPUVlA_MR4ULRbg1d97TGio912z4KP"), + 0), + yexception); + + UNIT_ASSERT(!priv.SignTicket("").empty()); + } + + Y_UNIT_TEST(TKeysPub) { + NRw::TRwPublicKey pub(Base64Decode("MIIBBAKCAQBwsRd4frsVARIVSfj_vCdfvA3Q9SsGhSybdBDhbm8L6rPqxdoSNLCdNXzDWj7Ppf0o8uWHMxC-5Lfw0I18ri68nhm9-ndixcnbn6ti1uetgkc28eiEP6Q8ILD_JmkynbUl1aKDNAa5XsK2vFSEX402uydRomsTn46kRY23hfqcIi0ohh5VxIrpclRsRZus0JFu-RJzhqTbKYV4y4dglWPGHh5BuTv9k_Oh0_Ra8Xp5Rith5vjaKZUQ5Hyh9UtBYTkNWdvXP9OpmbiLVeRLuMzBm4HEFHDwMZ1h6LSVP-wB_spJPaMLTn3Q3JIHe-wGBYRWzU51RRYDqv4O_H12w5C1")); + NRw::TRwPublicKey pub2(Base64Decode("MIIBBQKCAQEA4RATOfumLD1n6ICrW5biaAl9VldinczmkNPjpUWwc3gs8PnkCrtdnPFmpBwW3gjHdSNU1OuEg5A6K1o1xiGv9sU-jd88zQBOdK6E2zwnJnkK6bNusKE2H2CLqg3aMWCmTa9JbzSy1uO7wa-xCqqNUuCko-2lyv12HhL1ICIH951SHDa4qO1U5xZhhlUAnqWi9R4tYDeMiF41WdOjwT2fg8UkbusThmxa3yjCXjD7OyjshPtukN8Tl3UyGtV_s2CLnE3f28VAi-AVW8FtgL22xbGhuyEplXRrtF1E5oV7NSqxH1FS0SYROA8ffYQGV5tfx5WDFHiXDEP6BzoVfeBDRQ==")); + NRw::TRwPublicKey pub3(Base64Decode("MIGDAoGAX23ZgkYAmRFEWrp9aGLebVMVbVQ4TR_pmt9iEcCSmoaUqWHRBV95M0-l4mGLvnFfMJ7qhF5FSb7QNuoM2FNKELu4ZS_Ug1idEFBYfoT7kVzletsMVK4ZDDYRiM18fL8d58clfFAoCo-_EEMowqQeBXnxa0zqsLyNGL2x1f-KDY0=")); + + UNIT_ASSERT_EXCEPTION(NRw::TRwPublicKey("asdzxcv"), yexception); + UNIT_ASSERT_EXCEPTION(NRw::TRwPublicKey(Base64Decode("AoGAX23ZgkYAmRFEWrp9aGLebVMVbVQ4TR_pmt9iEcCSmoaUqWHRBV95M0-l4mGLvnFfMJ7qhF5FSb7QNuoM2FNKELu4ZS_Ug1idEFBYfoT7kVzletsMVK40")), yexception); + + UNIT_ASSERT(!pub.CheckSign("~~~", "~~~")); + } + + Y_UNIT_TEST(TKeys) { + NRw::TRwPrivateKey priv(Base64Decode("MIIEmwKCAQBwsRd4frsVARIVSfj_vCdfvA3Q9SsGhSybdBDhbm8L6rPqxdoSNLCdNXzDWj7Ppf0o8uWHMxC-5Lfw0I18ri68nhm9-ndixcnbn6ti1uetgkc28eiEP6Q8ILD_JmkynbUl1aKDNAa5XsK2vFSEX402uydRomsTn46kRY23hfqcIi0ohh5VxIrpclRsRZus0JFu-RJzhqTbKYV4y4dglWPGHh5BuTv9k_Oh0_Ra8Xp5Rith5vjaKZUQ5Hyh9UtBYTkNWdvXP9OpmbiLVeRLuMzBm4HEFHDwMZ1h6LSVP-wB_spJPaMLTn3Q3JIHe-wGBYRWzU51RRYDqv4O_H12w5C1AoGBALAwCQ7fdAPG1lGclL7iWFjUofwPCFwPyDjicDT_MRRu6_Ta4GjqOGO9zuOp0o_ePgvR-7nA0fbaspM4LZNrPZwmoYBCJMtKXetg68ylu2DO-RRSN2SSh1AIZSA_8UTABk69bPzNL31j4PyZWxrgZ3zP9uZvzggveuKt5ZhCMoB7AoGBAKO9oC2AZjLdh2RaEFotTL_dY6lVcm38VA6PnigB8gB_TMuSrd4xtRw5BxvHpOCnBcUAJE0dN4_DDe5mrotKYMD2_3_lcq9PaLZadrPDCSDL89wtoVxNQNAJTqFjBFXYNu4Ze63lrsqg45TF5XmVRemyBHzXw3erd0pJaeoUDaSPAoGAJhGoHx_nVw8sDoLzeRkOJ1_6-uh_wVmVr6407_LPjrrySEq-GiYu43M3-QDp8J_J9e3S1Rpm4nQX2bEf5Gx9n4wKz7Hp0cwkOqBOWhvrAu6YLpv59wslEtkx0LYcJy6yQk5mpU8l29rPO7b50NyLnfnE2za-9DyK038FKlr5VgICgYAUd7QFsAzGW7Dsi0ILRamX-6x1Kq5Nv4qB0fPFAD5AD-mZclW7xjajhyDjePScFOC4oASJo6bx-GG9zNXRaUwYHt_v_K5V6e0Wy07WeGEkGX57hbQriagaASnULGCKuwbdwy91vLXZVBxymLyvMqi9NkCPmvhu9W7pSS09QoG0kgKBgBYGASHb7oB42sozkpfcSwsalD-B4QuB-QccTgaf5iKN3X6bXA0dRwx3udx1OlH7x8F6P3c4Gj7bVlJnBbJtZ7OE1DAIRJlpS71sHXmUt2wZ3yKKRuySUOoBDKQH_iiYAMnXrZ-Zpe-sfB-TK2NcDO-Z_tzN-cEF71xVvLMIRlAPAoGAdeikZPh1O57RxnVY72asiMRZheMBhK-9uSNPyYEZv3bUnIjg4XdMYStF2yTHNu014XvkDSQTe-drv2BDs9ExKplM4xFOtDtPQQ3mMB3GoK1qVhM_9n1QEElreurMicahkalnPo6tU4Z6PFL7PTpjRnCN67lJp0J0fxNDL13YSagCgYBA9VJrMtPjzcAx5ZCIYJjrYUPqEG_ttQN2RJIHN3MVpdpLAMIgX3tnlfyLwQFVKK45D1JgFa_1HHcxTWGtdIX4nsIjPWt-cWCCCkkw9rM5_Iqcb-YLSood6IP2OK0w0XLD1STnFRy_BRwdjPbGOYmp6YrJDZAlajDkFSdRvsz9Vg=="), + 0); + NRw::TRwPublicKey pub(Base64Decode("MIIBBAKCAQBwsRd4frsVARIVSfj_vCdfvA3Q9SsGhSybdBDhbm8L6rPqxdoSNLCdNXzDWj7Ppf0o8uWHMxC-5Lfw0I18ri68nhm9-ndixcnbn6ti1uetgkc28eiEP6Q8ILD_JmkynbUl1aKDNAa5XsK2vFSEX402uydRomsTn46kRY23hfqcIi0ohh5VxIrpclRsRZus0JFu-RJzhqTbKYV4y4dglWPGHh5BuTv9k_Oh0_Ra8Xp5Rith5vjaKZUQ5Hyh9UtBYTkNWdvXP9OpmbiLVeRLuMzBm4HEFHDwMZ1h6LSVP-wB_spJPaMLTn3Q3JIHe-wGBYRWzU51RRYDqv4O_H12w5C1")); + + const TString data = "my magic data"; + + UNIT_ASSERT(pub.CheckSign(data, priv.SignTicket(data))); + UNIT_ASSERT(!pub.CheckSign("~~~~" + data, priv.SignTicket(data))); + UNIT_ASSERT(!pub.CheckSign(data, "~~~~" + priv.SignTicket(data))); + + UNIT_ASSERT(pub.CheckSign(data, + Base64Decode("EC5hZunmK3hOJZeov_XlNIXcwj5EsgX94lMd-tQJTNUO4NR6bCO7qQkKjEeFJmI2QFYXGY-iSf9WeMJ_brECAMyYAix-L8sZqcMPXD945QgkPsNQKyC0DX9FkgfSh6ZKkA-UvFSHrkn3QbeE9omk3-yXpqR-M8DlVqmp3mwdYlYRq0NdfTaD3AMXVA4aZTbW3OmhJoLJ8AxJ3w1oG5q_lk8dpW9vvqfIzsfPABme6sY5XyPmsjYaRDf9z4ZJgR-wTkG06_N_YzIklS5T2s_4FUKLz5gLMhsnVlNUpgZyRN9sXTAn9-zMJnCwAC8WRgykWnljPGDDJCjk-Xwsg7AOLQ=="))); + UNIT_ASSERT(pub.CheckSign(data, + Base64Decode("JbHSn1QEQeOEvzyt-LpawbQv4vPEEE05bWhjB2-MkoV-tyq9FykSqGqhP3ZFc1_FPrqguwEYrHibI2l5w3q8wnI1fcyRUoNuJxmBSzf2f_Uzn9ZoUSc7D9pTGSvK_hhZoL4YMc_VfbdEdnDuvHZNlZyaDPH9EbmUqyXjnXTEwRoK0fAU1rhlHvSZvnp0ctVBWSkaQsaU8dJTKDBtIQVP1D5Py2pKB2NBF_Ytz2thWt7iLjbTyjtis6DC-JKwjFBqv6nQf42sKalHQqWFuIvBCIfNUswEw4_sGfwWVSBBmFplf7FmD7sN8znUahYUPGCe1uFNly6WwpPJsm8VtiU80g=="))); + UNIT_ASSERT(pub.CheckSign(data, + Base64Decode("FeMZtDP-yuoNqK2HYw3JxTV9v7p8IoQEuRMtuHddafh4bq1ZOeEqg7g7Su6M3iq_kN9DZ_fVhuhuVcbZmNYPIvJ8oL5DE80KI3d1Qbs9mS8_X4Oq2TJpZgNfFG-z_LPRZSNRP9Q8sQhlAoSZHOSZkBFcYj1EuqEp6nSSSbX8Ji4Se-TfhIh3YFQkr-Ivk_3NmSXhDXUaW7CHo2rVm58QJ2cgSEuxzBH-Q8E8tGDCEmk4p3_iot9XY8RRN-_j0yi15etmXCUIKFbpDogtHdT8CyAEVHMYvsLqkLux9pzy3RdvNQmoPjol3wIm-H0wMtF_pMw4G2QLNev6he6xWeckxw=="))); + } + + Y_UNIT_TEST(Keygen) { + for (size_t idx = 0; idx < 100; ++idx) { + NRw::TKeyPair pair = NRw::GenKeyPair(1024); + NRw::TRwPrivateKey priv(pair.Private, 0); + NRw::TRwPublicKey pub(pair.Public); + + const TString data = "my magic data"; + TStringStream s; + s << "data='" << data << "'."; + s << "private='" << Base64Encode(pair.Private) << "'."; + s << "public='" << Base64Encode(pair.Public) << "'."; + TString sign; + UNIT_ASSERT_NO_EXCEPTION_C(sign = priv.SignTicket(data), s.Str()); + s << "sign='" << Base64Encode(sign) << "'."; + UNIT_ASSERT_C(pub.CheckSign(data, sign), s.Str()); + } + } +} diff --git a/library/cpp/tvmauth/src/rw/ut_large/gen/main.cpp b/library/cpp/tvmauth/src/rw/ut_large/gen/main.cpp new file mode 100644 index 0000000000..31a599c996 --- /dev/null +++ b/library/cpp/tvmauth/src/rw/ut_large/gen/main.cpp @@ -0,0 +1,32 @@ +#include <library/cpp/tvmauth/src/rw/keys.h> + +#include <library/cpp/string_utils/base64/base64.h> + +#include <util/generic/yexception.h> + +using namespace NTvmAuth; + +const TString DATA = "my magic data"; + +int main(int, char**) { + const NRw::TKeyPair pair = NRw::GenKeyPair(1024); + const NRw::TRwPrivateKey priv(pair.Private, 0); + const NRw::TRwPublicKey pub(pair.Public); + + Cout << "data='" << DATA << "'." + << "private='" << Base64Encode(pair.Private) << "'." + << "public='" << Base64Encode(pair.Public) << "'."; + + TString sign; + try { + sign = priv.SignTicket(DATA); + Cout << "sign='" << Base64Encode(sign) << "'."; + Y_ENSURE(pub.CheckSign(DATA, sign)); + } catch (const std::exception& e) { + Cout << "what='" << e.what() << "'" << Endl; + return 1; + } + Cout << Endl; + + return 0; +} diff --git a/library/cpp/tvmauth/src/rw/ut_large/test.py b/library/cpp/tvmauth/src/rw/ut_large/test.py new file mode 100644 index 0000000000..0cf95d9848 --- /dev/null +++ b/library/cpp/tvmauth/src/rw/ut_large/test.py @@ -0,0 +1,35 @@ +from __future__ import print_function + +import os +import subprocess +import sys + +import yatest.common as yc + + +def test_fuzzing(): + errfile = './errfile' + outfile = './outfile' + env = os.environ.copy() + + for number in range(25000): + with open(errfile, 'w') as fe: + with open(outfile, 'w') as fo: + p = subprocess.Popen( + [ + yc.build_path('library/cpp/tvmauth/src/rw/ut_large/gen/gen'), + ], + env=env, + stdout=fo, + stderr=fe, + ) + code = p.wait() + + with open(errfile) as fe: + all = fe.read() + if all != '': + with open(outfile) as fo: + print(fo.read(), file=sys.stderr) + assert all == '' + + assert code == 0 diff --git a/library/cpp/tvmauth/src/service_impl.cpp b/library/cpp/tvmauth/src/service_impl.cpp new file mode 100644 index 0000000000..463b5d634c --- /dev/null +++ b/library/cpp/tvmauth/src/service_impl.cpp @@ -0,0 +1,204 @@ +#include "service_impl.h" + +#include "parser.h" +#include "utils.h" + +#include <library/cpp/tvmauth/exception.h> +#include <library/cpp/tvmauth/ticket_status.h> + +#include <util/generic/strbuf.h> +#include <util/string/cast.h> +#include <util/string/split.h> + +namespace NTvmAuth { + static const char* EX_MSG = "Method cannot be used in non-valid ticket"; + + TCheckedServiceTicket::TImpl::operator bool() const { + return (Status_ == ETicketStatus::Ok); + } + + TTvmId TCheckedServiceTicket::TImpl::GetDst() const { + Y_ENSURE_EX(bool(*this), TNotAllowedException() << EX_MSG); + return ProtobufTicket_.service().dstclientid(); + } + + TTvmId TCheckedServiceTicket::TImpl::GetSrc() const { + Y_ENSURE_EX(bool(*this), TNotAllowedException() << EX_MSG); + return ProtobufTicket_.service().srcclientid(); + } + + const TScopes& TCheckedServiceTicket::TImpl::GetScopes() const { + Y_ENSURE_EX(bool(*this), TNotAllowedException() << EX_MSG); + if (CachedScopes_.empty()) { + for (const auto& el : ProtobufTicket_.service().scopes()) { + CachedScopes_.push_back(el); + } + } + return CachedScopes_; + } + + bool TCheckedServiceTicket::TImpl::HasScope(TStringBuf scopeName) const { + Y_ENSURE_EX(bool(*this), TNotAllowedException() << EX_MSG); + return std::binary_search(ProtobufTicket_.service().scopes().begin(), ProtobufTicket_.service().scopes().end(), scopeName); + } + + ETicketStatus TCheckedServiceTicket::TImpl::GetStatus() const { + return Status_; + } + + time_t TCheckedServiceTicket::TImpl::GetExpirationTime() const { + Y_ENSURE_EX(bool(*this), TNotAllowedException() << EX_MSG); + return ProtobufTicket_.expirationtime(); + } + + TString TCheckedServiceTicket::TImpl::DebugInfo() const { + if (CachedDebugInfo_) { + return CachedDebugInfo_; + } + + if (Status_ == ETicketStatus::Malformed) { + CachedDebugInfo_ = "status=malformed;"; + return CachedDebugInfo_; + } + + TString targetString = "ticket_type="; + targetString.reserve(256); + if (Status_ == ETicketStatus::InvalidTicketType) { + targetString.append("not-serv;"); + CachedDebugInfo_ = targetString; + return targetString; + } + + targetString.append("serv"); + if (ProtobufTicket_.has_expirationtime()) + targetString.append(";expiration_time=").append(IntToString<10>(ProtobufTicket_.expirationtime())); + if (ProtobufTicket_.service().has_srcclientid()) { + targetString.append(";src=").append(IntToString<10>(ProtobufTicket_.service().srcclientid())); + } + if (ProtobufTicket_.service().has_dstclientid()) { + targetString.append(";dst=").append(IntToString<10>(ProtobufTicket_.service().dstclientid())); + } + for (const auto& scope : ProtobufTicket_.service().scopes()) { + targetString.append(";scope=").append(scope); + } + if (ProtobufTicket_.service().has_issueruid()) { + targetString.append(";issuer_uid=").append(IntToString<10>(ProtobufTicket_.service().GetissuerUid())); + } + targetString.append(";"); + + CachedDebugInfo_ = targetString; + return targetString; + } + + TMaybe<TUid> TCheckedServiceTicket::TImpl::GetIssuerUid() const { + Y_ENSURE_EX(bool(*this), TNotAllowedException() << EX_MSG); + return ProtobufTicket_.service().has_issueruid() + ? ProtobufTicket_.service().GetissuerUid() + : TMaybe<TUid>(); + } + + void TCheckedServiceTicket::TImpl::SetStatus(ETicketStatus status) { + Status_ = status; + } + + TCheckedServiceTicket::TImpl::TImpl(ETicketStatus status, ticket2::Ticket&& protobufTicket) + : Status_(status) + , ProtobufTicket_(std::move(protobufTicket)) + { + } + + TServiceTicketImplPtr TCheckedServiceTicket::TImpl::CreateTicketForTests(ETicketStatus status, + TTvmId src, + TMaybe<TUid> issuerUid) { + ticket2::Ticket proto; + proto.mutable_service()->set_srcclientid(src); + proto.mutable_service()->set_dstclientid(100500); + if (issuerUid) { + proto.mutable_service()->set_issueruid(*issuerUid); + } + return MakeHolder<TImpl>(status, std::move(proto)); + } + + TServiceContext::TImpl::TImpl(TStringBuf secretBase64, TTvmId selfTvmId, TStringBuf tvmKeysResponse) + : Secret_(ParseSecret(secretBase64)) + , SelfTvmId_(selfTvmId) + { + ResetKeys(tvmKeysResponse); + } + + TServiceContext::TImpl::TImpl(TTvmId selfTvmId, TStringBuf tvmKeysResponse) + : SelfTvmId_(selfTvmId) + { + ResetKeys(tvmKeysResponse); + } + + TServiceContext::TImpl::TImpl(TStringBuf secretBase64) + : Secret_(ParseSecret(secretBase64)) + { + } + + void TServiceContext::TImpl::ResetKeys(TStringBuf tvmKeysResponse) { + tvm_keys::Keys protoKeys; + if (!protoKeys.ParseFromString(TParserTvmKeys::ParseStrV1(tvmKeysResponse))) { + ythrow TMalformedTvmKeysException() << "Malformed TVM keys"; + } + + NRw::TPublicKeys keys; + for (int idx = 0; idx < protoKeys.tvm_size(); ++idx) { + const tvm_keys::TvmKey& k = protoKeys.tvm(idx); + keys.emplace(k.gen().id(), + k.gen().body()); + } + + if (keys.empty()) { + ythrow TEmptyTvmKeysException() << "Empty TVM keys"; + } + + Keys_ = std::move(keys); + } + + TServiceTicketImplPtr TServiceContext::TImpl::Check(TStringBuf ticketBody, const TServiceContext::TCheckFlags& flags) const { + Y_ENSURE_EX(!Keys_.empty(), TEmptyTvmKeysException() << "Empty TVM keys"); + + TParserTickets::TRes res = TParserTickets::ParseV3(ticketBody, Keys_, TParserTickets::ServiceFlag()); + + if (res.Status != ETicketStatus::Ok) { + return MakeHolder<TCheckedServiceTicket::TImpl>(res.Status, std::move(res.Ticket)); + } + + if (!res.Ticket.has_service()) { + return MakeHolder<TCheckedServiceTicket::TImpl>(ETicketStatus::Malformed, std::move(res.Ticket)); + } + + if (flags.NeedDstCheck && SelfTvmId_ != res.Ticket.service().dstclientid()) { + return MakeHolder<TCheckedServiceTicket::TImpl>(ETicketStatus::InvalidDst, std::move(res.Ticket)); + } + + return MakeHolder<TCheckedServiceTicket::TImpl>(ETicketStatus::Ok, std::move(res.Ticket)); + } + + TString TServiceContext::TImpl::SignCgiParamsForTvm(TStringBuf ts, TStringBuf dst, TStringBuf scopes) const { + if (Secret_.Value().empty()) { + ythrow TMalformedTvmSecretException() << "Malformed TVM secret: it is empty"; + } + return NUtils::SignCgiParamsForTvm(Secret_, ts, dst, scopes); + } + + TString TServiceContext::TImpl::ParseSecret(TStringBuf secretBase64) { + while (secretBase64 && secretBase64.back() == '\n') { + secretBase64.Chop(1); + } + + if (secretBase64.empty()) { + ythrow TMalformedTvmSecretException() << "Malformed TVM secret: it is empty"; + } + + const TString secret = NUtils::Base64url2bin(secretBase64); + if (secret.empty()) { + ythrow TMalformedTvmSecretException() << "Malformed TVM secret: invalid base64url"; + } + + return secret; + } + +} diff --git a/library/cpp/tvmauth/src/service_impl.h b/library/cpp/tvmauth/src/service_impl.h new file mode 100644 index 0000000000..26202f283e --- /dev/null +++ b/library/cpp/tvmauth/src/service_impl.h @@ -0,0 +1,77 @@ +#pragma once + +#include <library/cpp/tvmauth/src/protos/ticket2.pb.h> +#include <library/cpp/tvmauth/src/protos/tvm_keys.pb.h> +#include <library/cpp/tvmauth/src/rw/keys.h> + +#include <library/cpp/tvmauth/type.h> +#include <library/cpp/tvmauth/deprecated/service_context.h> + +#include <library/cpp/charset/ci_string.h> +#include <library/cpp/string_utils/secret_string/secret_string.h> + +#include <util/generic/maybe.h> + +#include <string> + +namespace NTvmAuth { + using TServiceTicketImplPtr = THolder<TCheckedServiceTicket::TImpl>; + class TCheckedServiceTicket::TImpl { + public: + explicit operator bool() const; + + TTvmId GetDst() const; + TTvmId GetSrc() const; + const TScopes& GetScopes() const; + bool HasScope(TStringBuf scopeName) const; + ETicketStatus GetStatus() const; + time_t GetExpirationTime() const; + + TString DebugInfo() const; + TMaybe<TUid> GetIssuerUid() const; + + void SetStatus(ETicketStatus status); + + /*! + * Constructor for creation invalid ticket storing error status in TServiceContext + * @param status + * @param protobufTicket + */ + TImpl(ETicketStatus status, ticket2::Ticket&& protobufTicket); + + static TServiceTicketImplPtr CreateTicketForTests(ETicketStatus status, + TTvmId src, + TMaybe<TUid> issuerUid); + + private: + ETicketStatus Status_; + ticket2::Ticket ProtobufTicket_; + mutable TScopes CachedScopes_; + mutable TString CachedDebugInfo_; + }; + + class TServiceContext::TImpl { + public: + TImpl(TStringBuf secretBase64, TTvmId selfTvmId, TStringBuf tvmKeysResponse); + TImpl(TTvmId selfTvmId, TStringBuf tvmKeysResponse); + TImpl(TStringBuf secretBase64); + + void ResetKeys(TStringBuf tvmKeysResponse); + + TServiceTicketImplPtr Check(TStringBuf ticketBody, const TServiceContext::TCheckFlags& flags = {}) const; + TString SignCgiParamsForTvm(TStringBuf ts, TStringBuf dst, TStringBuf scopes = TStringBuf()) const; + + const NRw::TPublicKeys& GetKeys() const { // for tests + return Keys_; + } + + private: + static TString ParseSecret(TStringBuf secretBase64); + + NRw::TPublicKeys Keys_; + const NSecretString::TSecretString Secret_; + const TTvmId SelfTvmId_ = 0; + + ::google::protobuf::LogSilencer LogSilencer_; + }; +} diff --git a/library/cpp/tvmauth/src/service_ticket.cpp b/library/cpp/tvmauth/src/service_ticket.cpp new file mode 100644 index 0000000000..9716836cf5 --- /dev/null +++ b/library/cpp/tvmauth/src/service_ticket.cpp @@ -0,0 +1,46 @@ +#include "service_impl.h" + +#include <library/cpp/tvmauth/checked_service_ticket.h> + +namespace NTvmAuth { + static const char* EX_MSG = "Ticket already moved out"; + + TCheckedServiceTicket::TCheckedServiceTicket(THolder<TImpl> impl) + : Impl_(std::move(impl)) + { + } + + TCheckedServiceTicket::TCheckedServiceTicket(TCheckedServiceTicket&& o) = default; + TCheckedServiceTicket& TCheckedServiceTicket::operator=(TCheckedServiceTicket&& o) = default; + TCheckedServiceTicket::~TCheckedServiceTicket() = default; + + TCheckedServiceTicket::operator bool() const { + Y_ENSURE(Impl_, EX_MSG); + return Impl_->operator bool(); + } + + TTvmId TCheckedServiceTicket::GetDst() const { + Y_ENSURE(Impl_, EX_MSG); + return Impl_->GetDst(); + } + + TTvmId TCheckedServiceTicket::GetSrc() const { + Y_ENSURE(Impl_, EX_MSG); + return Impl_->GetSrc(); + } + + ETicketStatus TCheckedServiceTicket::GetStatus() const { + Y_ENSURE(Impl_, EX_MSG); + return Impl_->GetStatus(); + } + + TString TCheckedServiceTicket::DebugInfo() const { + Y_ENSURE(Impl_, EX_MSG); + return Impl_->DebugInfo(); + } + + TMaybe<TUid> TCheckedServiceTicket::GetIssuerUid() const { + Y_ENSURE(Impl_, EX_MSG); + return Impl_->GetIssuerUid(); + } +} diff --git a/library/cpp/tvmauth/src/status.cpp b/library/cpp/tvmauth/src/status.cpp new file mode 100644 index 0000000000..1b08fc098f --- /dev/null +++ b/library/cpp/tvmauth/src/status.cpp @@ -0,0 +1,32 @@ +#include <library/cpp/tvmauth/ticket_status.h> + +#include <util/generic/yexception.h> + +namespace NTvmAuth { + TStringBuf StatusToString(ETicketStatus st) { + switch (st) { + case ETicketStatus::Ok: + return "OK"; + case ETicketStatus::Expired: + return "Expired ticket"; + case ETicketStatus::InvalidBlackboxEnv: + return "Invalid BlackBox environment"; + case ETicketStatus::InvalidDst: + return "Invalid ticket destination"; + case ETicketStatus::InvalidTicketType: + return "Invalid ticket type"; + case ETicketStatus::Malformed: + return "Malformed ticket"; + case ETicketStatus::MissingKey: + return "Context does not have required key to check ticket: public keys are too old"; + case ETicketStatus::SignBroken: + return "Invalid ticket signature"; + case ETicketStatus::UnsupportedVersion: + return "Unsupported ticket version"; + case ETicketStatus::NoRoles: + return "Subject (src or defaultUid) does not have any roles in IDM"; + } + + ythrow yexception() << "Unexpected status: " << static_cast<int>(st); + } +} diff --git a/library/cpp/tvmauth/src/unittest.cpp b/library/cpp/tvmauth/src/unittest.cpp new file mode 100644 index 0000000000..5133d79ea9 --- /dev/null +++ b/library/cpp/tvmauth/src/unittest.cpp @@ -0,0 +1,14 @@ +#include "service_impl.h" +#include "user_impl.h" + +#include <library/cpp/tvmauth/unittest.h> + +namespace NTvmAuth::NUnittest { + TCheckedServiceTicket CreateServiceTicket(ETicketStatus status, TTvmId src, TMaybe<TUid> issuerUid) { + return TCheckedServiceTicket(TCheckedServiceTicket::TImpl::CreateTicketForTests(status, src, issuerUid)); + } + + TCheckedUserTicket CreateUserTicket(ETicketStatus status, TUid defaultUid, const TScopes& scopes, const TUids& uids, EBlackboxEnv env) { + return TCheckedUserTicket(TCheckedUserTicket::TImpl::CreateTicketForTests(status, defaultUid, scopes, uids, env)); + } +} diff --git a/library/cpp/tvmauth/src/user_impl.cpp b/library/cpp/tvmauth/src/user_impl.cpp new file mode 100644 index 0000000000..4fda799aac --- /dev/null +++ b/library/cpp/tvmauth/src/user_impl.cpp @@ -0,0 +1,241 @@ +#include "user_impl.h" + +#include "parser.h" + +#include <library/cpp/tvmauth/exception.h> +#include <library/cpp/tvmauth/ticket_status.h> + +#include <util/generic/strbuf.h> +#include <util/string/cast.h> +#include <util/string/split.h> + +#include <algorithm> + +namespace NTvmAuth { + static const char* EX_MSG = "Method cannot be used in non-valid ticket"; + + TStringBuf GetBlackboxEnvAsString(EBlackboxEnv environment) { + switch (environment) { + case (EBlackboxEnv::Prod): + return TStringBuf("Prod"); + case (EBlackboxEnv::Test): + return TStringBuf("Test"); + case (EBlackboxEnv::ProdYateam): + return TStringBuf("ProdYateam"); + case (EBlackboxEnv::TestYateam): + return TStringBuf("TestYateam"); + case (EBlackboxEnv::Stress): + return TStringBuf("Stress"); + default: + throw yexception() << "Unknown environment"; + } + } + + TCheckedUserTicket::TImpl::operator bool() const { + return (Status_ == ETicketStatus::Ok); + } + + TUid TCheckedUserTicket::TImpl::GetDefaultUid() const { + Y_ENSURE_EX(bool(*this), TNotAllowedException() << EX_MSG); + return ProtobufTicket_.user().defaultuid(); + } + + time_t TCheckedUserTicket::TImpl::GetExpirationTime() const { + Y_ENSURE_EX(bool(*this), TNotAllowedException() << EX_MSG); + return ProtobufTicket_.expirationtime(); + } + + const TScopes& TCheckedUserTicket::TImpl::GetScopes() const { + Y_ENSURE_EX(bool(*this), TNotAllowedException() << EX_MSG); + if (CachedScopes_.empty()) { + for (const auto& el : ProtobufTicket_.user().scopes()) { + CachedScopes_.push_back(el); + } + } + return CachedScopes_; + } + + bool TCheckedUserTicket::TImpl::HasScope(TStringBuf scopeName) const { + Y_ENSURE_EX(bool(*this), TNotAllowedException() << EX_MSG); + return std::binary_search(ProtobufTicket_.user().scopes().begin(), ProtobufTicket_.user().scopes().end(), scopeName); + } + + ETicketStatus TCheckedUserTicket::TImpl::GetStatus() const { + return Status_; + } + + const TUids& TCheckedUserTicket::TImpl::GetUids() const { + Y_ENSURE_EX(bool(*this), TNotAllowedException() << EX_MSG); + if (CachedUids_.empty()) { + for (const auto& user : ProtobufTicket_.user().users()) { + CachedUids_.push_back(user.uid()); + } + } + return CachedUids_; + } + + TString TCheckedUserTicket::TImpl::DebugInfo() const { + if (CachedDebugInfo_) { + return CachedDebugInfo_; + } + + if (Status_ == ETicketStatus::Malformed) { + CachedDebugInfo_ = "status=malformed;"; + return CachedDebugInfo_; + } + + TString targetString = "ticket_type="; + targetString.reserve(256); + if (Status_ == ETicketStatus::InvalidTicketType) { + targetString.append("not-user;"); + CachedDebugInfo_ = targetString; + return targetString; + } + + targetString.append("user"); + if (ProtobufTicket_.expirationtime() > 0) + targetString.append(";expiration_time=").append(IntToString<10>(ProtobufTicket_.expirationtime())); + for (const auto& scope : ProtobufTicket_.user().scopes()) { + targetString.append(";scope=").append(scope); + } + + if (ProtobufTicket_.user().defaultuid() > 0) + targetString.append(";default_uid=").append(IntToString<10>(ProtobufTicket_.user().defaultuid())); + for (const auto& user : ProtobufTicket_.user().users()) { + targetString.append(";uid=").append(IntToString<10>(user.uid())); + } + + targetString.append(";env="); + EBlackboxEnv environment = static_cast<EBlackboxEnv>(ProtobufTicket_.user().env()); + targetString.append(GetBlackboxEnvAsString(environment)); + targetString.append(";"); + + CachedDebugInfo_ = targetString; + return targetString; + } + + EBlackboxEnv TCheckedUserTicket::TImpl::GetEnv() const { + return (EBlackboxEnv)ProtobufTicket_.user().env(); + } + + void TCheckedUserTicket::TImpl::SetStatus(ETicketStatus status) { + Status_ = status; + } + + TCheckedUserTicket::TImpl::TImpl(ETicketStatus status, ticket2::Ticket&& protobufTicket) + : Status_(status) + , ProtobufTicket_(std::move(protobufTicket)) + { + } + + TUserTicketImplPtr TCheckedUserTicket::TImpl::CreateTicketForTests(ETicketStatus status, + TUid defaultUid, + TScopes scopes, + TUids uids, + EBlackboxEnv env) { + auto prepareCont = [](auto& cont) { + std::sort(cont.begin(), cont.end()); + cont.erase(std::unique(cont.begin(), cont.end()), cont.end()); + }; + auto erase = [](auto& cont, auto val) { + auto it = std::find(cont.begin(), cont.end(), val); + if (it != cont.end()) { + cont.erase(it); + } + }; + + prepareCont(scopes); + erase(scopes, ""); + + uids.push_back(defaultUid); + prepareCont(uids); + erase(uids, 0); + Y_ENSURE(!uids.empty(), "User ticket cannot contain empty uid list"); + + ticket2::Ticket proto; + for (TUid uid : uids) { + proto.mutable_user()->add_users()->set_uid(uid); + } + proto.mutable_user()->set_defaultuid(defaultUid); + proto.mutable_user()->set_entrypoint(100500); + for (TStringBuf scope : scopes) { + proto.mutable_user()->add_scopes(TString(scope)); + } + + proto.mutable_user()->set_env((tvm_keys::BbEnvType)env); + + return MakeHolder<TImpl>(status, std::move(proto)); + } + + TUserContext::TImpl::TImpl(EBlackboxEnv env, TStringBuf tvmKeysResponse) + : Env_(env) + { + ResetKeys(tvmKeysResponse); + } + + void TUserContext::TImpl::ResetKeys(TStringBuf tvmKeysResponse) { + tvm_keys::Keys protoKeys; + if (!protoKeys.ParseFromString(TParserTvmKeys::ParseStrV1(tvmKeysResponse))) { + ythrow TMalformedTvmKeysException() << "Malformed TVM keys"; + } + + NRw::TPublicKeys keys; + for (int idx = 0; idx < protoKeys.bb_size(); ++idx) { + const tvm_keys::BbKey& k = protoKeys.bb(idx); + if (IsAllowed(k.env())) { + keys.emplace(k.gen().id(), + k.gen().body()); + } + } + + if (keys.empty()) { + ythrow TEmptyTvmKeysException() << "Empty TVM keys"; + } + + Keys_ = std::move(keys); + } + + TUserTicketImplPtr TUserContext::TImpl::Check(TStringBuf ticketBody) const { + TParserTickets::TRes res = TParserTickets::ParseV3(ticketBody, Keys_, TParserTickets::UserFlag()); + ETicketStatus status = CheckProtobufUserTicket(res.Ticket); + + if (res.Status != ETicketStatus::Ok && !(res.Status == ETicketStatus::MissingKey && status == ETicketStatus::InvalidBlackboxEnv)) { + status = res.Status; + } + return MakeHolder<TCheckedUserTicket::TImpl>(status, std::move(res.Ticket)); + } + + ETicketStatus TUserContext::TImpl::CheckProtobufUserTicket(const ticket2::Ticket& ticket) const { + if (!ticket.has_user()) { + return ETicketStatus::Malformed; + } + if (!IsAllowed(ticket.user().env())) { + return ETicketStatus::InvalidBlackboxEnv; + } + return ETicketStatus::Ok; + } + + const NRw::TPublicKeys& TUserContext::TImpl::GetKeys() const { + return Keys_; + } + + bool TUserContext::TImpl::IsAllowed(tvm_keys::BbEnvType env) const { + if (env == tvm_keys::Prod && (Env_ == EBlackboxEnv::Prod || Env_ == EBlackboxEnv::Stress)) { + return true; + } + if (env == tvm_keys::ProdYateam && Env_ == EBlackboxEnv::ProdYateam) { + return true; + } + if (env == tvm_keys::Test && Env_ == EBlackboxEnv::Test) { + return true; + } + if (env == tvm_keys::TestYateam && Env_ == EBlackboxEnv::TestYateam) { + return true; + } + if (env == tvm_keys::Stress && Env_ == EBlackboxEnv::Stress) { + return true; + } + + return false; + } +} diff --git a/library/cpp/tvmauth/src/user_impl.h b/library/cpp/tvmauth/src/user_impl.h new file mode 100644 index 0000000000..292e94bf5b --- /dev/null +++ b/library/cpp/tvmauth/src/user_impl.h @@ -0,0 +1,72 @@ +#pragma once + +#include <library/cpp/tvmauth/src/protos/ticket2.pb.h> +#include <library/cpp/tvmauth/src/protos/tvm_keys.pb.h> +#include <library/cpp/tvmauth/src/rw/keys.h> + +#include <library/cpp/tvmauth/deprecated/user_context.h> + +#include <library/cpp/charset/ci_string.h> + +#include <unordered_map> + +namespace NTvmAuth { + using TUserTicketImplPtr = THolder<TCheckedUserTicket::TImpl>; + class TCheckedUserTicket::TImpl { + public: + explicit operator bool() const; + + TUid GetDefaultUid() const; + time_t GetExpirationTime() const; + const TScopes& GetScopes() const; + bool HasScope(TStringBuf scopeName) const; + ETicketStatus GetStatus() const; + const TUids& GetUids() const; + + TString DebugInfo() const; + + EBlackboxEnv GetEnv() const; + + void SetStatus(ETicketStatus status); + + /*! + * Constructor for creation invalid ticket storing error status in TServiceContext + * @param status + * @param protobufTicket + */ + TImpl(ETicketStatus status, ticket2::Ticket&& protobufTicket); + + static TUserTicketImplPtr CreateTicketForTests(ETicketStatus status, + TUid defaultUid, + TScopes scopes, + TUids uids, + EBlackboxEnv env = EBlackboxEnv::Test); + + private: + static const int MaxUserCount = 15; + + ETicketStatus Status_; + ticket2::Ticket ProtobufTicket_; + mutable TScopes CachedScopes_; + mutable TUids CachedUids_; + mutable TString CachedDebugInfo_; + }; + + class TUserContext::TImpl { + public: + TImpl(EBlackboxEnv env, TStringBuf tvmKeysResponse); + void ResetKeys(TStringBuf tvmKeysResponse); + + TUserTicketImplPtr Check(TStringBuf ticketBody) const; + const NRw::TPublicKeys& GetKeys() const; + + bool IsAllowed(tvm_keys::BbEnvType env) const; + + private: + ETicketStatus CheckProtobufUserTicket(const ticket2::Ticket& ticket) const; + + NRw::TPublicKeys Keys_; + EBlackboxEnv Env_; + ::google::protobuf::LogSilencer LogSilencer_; + }; +} diff --git a/library/cpp/tvmauth/src/user_ticket.cpp b/library/cpp/tvmauth/src/user_ticket.cpp new file mode 100644 index 0000000000..3e4e0c0364 --- /dev/null +++ b/library/cpp/tvmauth/src/user_ticket.cpp @@ -0,0 +1,56 @@ +#include "user_impl.h" + +#include <library/cpp/tvmauth/checked_user_ticket.h> + +namespace NTvmAuth { + static const char* EX_MSG = "Ticket already moved out"; + + TCheckedUserTicket::TCheckedUserTicket(THolder<TCheckedUserTicket::TImpl> impl) + : Impl_(std::move(impl)) + { + } + + TCheckedUserTicket::TCheckedUserTicket(TCheckedUserTicket&& o) = default; + TCheckedUserTicket::~TCheckedUserTicket() = default; + TCheckedUserTicket& TCheckedUserTicket::operator=(TCheckedUserTicket&& o) = default; + + TCheckedUserTicket::operator bool() const { + Y_ENSURE(Impl_, EX_MSG); + return Impl_->operator bool(); + } + + const TUids& TCheckedUserTicket::GetUids() const { + Y_ENSURE(Impl_, EX_MSG); + return Impl_->GetUids(); + } + + TUid TCheckedUserTicket::GetDefaultUid() const { + Y_ENSURE(Impl_, EX_MSG); + return Impl_->GetDefaultUid(); + } + + const TScopes& TCheckedUserTicket::GetScopes() const { + Y_ENSURE(Impl_, EX_MSG); + return Impl_->GetScopes(); + } + + bool TCheckedUserTicket::HasScope(TStringBuf scopeName) const { + Y_ENSURE(Impl_, EX_MSG); + return Impl_->HasScope(scopeName); + } + + ETicketStatus TCheckedUserTicket::GetStatus() const { + Y_ENSURE(Impl_, EX_MSG); + return Impl_->GetStatus(); + } + + TString TCheckedUserTicket::DebugInfo() const { + Y_ENSURE(Impl_, EX_MSG); + return Impl_->DebugInfo(); + } + + EBlackboxEnv TCheckedUserTicket::GetEnv() const { + Y_ENSURE(Impl_, EX_MSG); + return Impl_->GetEnv(); + } +} diff --git a/library/cpp/tvmauth/src/ut/parser_ut.cpp b/library/cpp/tvmauth/src/ut/parser_ut.cpp new file mode 100644 index 0000000000..530f45331a --- /dev/null +++ b/library/cpp/tvmauth/src/ut/parser_ut.cpp @@ -0,0 +1,143 @@ +#include <library/cpp/tvmauth/src/parser.h> +#include <library/cpp/tvmauth/src/utils.h> + +#include <library/cpp/tvmauth/exception.h> +#include <library/cpp/tvmauth/ticket_status.h> + +#include <library/cpp/testing/unittest/registar.h> + +Y_UNIT_TEST_SUITE(ParserTestSuite) { + using namespace NTvmAuth; + + Y_UNIT_TEST(Keys) { + UNIT_ASSERT_EXCEPTION(TParserTvmKeys::ParseStrV1("2:asds"), TMalformedTvmKeysException); + UNIT_ASSERT_EXCEPTION(TParserTvmKeys::ParseStrV1("3:asds"), TMalformedTvmKeysException); + UNIT_ASSERT_EXCEPTION(TParserTvmKeys::ParseStrV1("1:+a/sds"), TMalformedTvmKeysException); + + UNIT_ASSERT_VALUES_EQUAL("sdsd", NUtils::Bin2base64url(TParserTvmKeys::ParseStrV1("1:sdsd"))); + } + + Y_UNIT_TEST(TicketsStrV3) { + UNIT_ASSERT_EQUAL(TParserTickets::TStrRes({ETicketStatus::Ok, + NUtils::Base64url2bin("CgYIDRCUkQYQDBgcIgdiYjpzZXNzIghiYjpzZXNzMg"), + NUtils::Base64url2bin("ERmeH_yzC7K_QsoHTyw7llCsyExEz3CoEopPIuivA0ZAtTaFq_Pa0l9Fhhx_NX9WpOp2CPyY5cFc4PXhcO83jCB7-EGvHNxGN-j2NQalERzPiKqkDCO0Q5etLzSzrfTlvMz7sXDvELNBHyA0PkAQnbz4supY0l-0Q6JBYSEF3zOVMjjE-HeQIFL3ats3_PakaUMWRvgQQ88pVdYZqAtbDw9PlTla7ommygVZQjcfNFXV1pJKRgOCLs-YyCjOJHLKL04zYj0X6KsOCTUeqhj7ml96wLZ-g1X9tyOR2WAr2Ctq7wIEHwqhxOLgOSKqm05xH6Vi3E_hekf50oe2jPfKEA"), + "3:serv:CgYIDRCUkQYQDBgcIgdiYjpzZXNzIghiYjpzZXNzMg:"}), + TParserTickets::ParseStrV3("3:serv:CgYIDRCUkQYQDBgcIgdiYjpzZXNzIghiYjpzZXNzMg:ERmeH_yzC7K_QsoHTyw7llCsyExEz3CoEopPIuivA0ZAtTaFq_Pa0l9Fhhx_NX9WpOp2CPyY5cFc4PXhcO83jCB7-EGvHNxGN-j2NQalERzPiKqkDCO0Q5etLzSzrfTlvMz7sXDvELNBHyA0PkAQnbz4supY0l-0Q6JBYSEF3zOVMjjE-HeQIFL3ats3_PakaUMWRvgQQ88pVdYZqAtbDw9PlTla7ommygVZQjcfNFXV1pJKRgOCLs-YyCjOJHLKL04zYj0X6KsOCTUeqhj7ml96wLZ-g1X9tyOR2WAr2Ctq7wIEHwqhxOLgOSKqm05xH6Vi3E_hekf50oe2jPfKEA", + TParserTickets::ServiceFlag())); + UNIT_ASSERT_EQUAL(TParserTickets::TStrRes({ETicketStatus::UnsupportedVersion, + {}, + {}, + {}}), + TParserTickets::ParseStrV3("2:serv:CgYIDRCUkQYQDBgcIgdiYjpzZXNzIghiYjpzZXNzMg:ERmeH_yzC7K_QsoHTyw7llCsyExEz3CoEopPIuivA0ZAtTaFq_Pa0l9Fhhx_NX9WpOp2CPyY5cFc4PXhcO83jCB7-EGvHNxGN-j2NQalERzPiKqkDCO0Q5etLzSzrfTlvMz7sXDvELNBHyA0PkAQnbz4supY0l-0Q6JBYSEF3zOVMjjE-HeQIFL3ats3_PakaUMWRvgQQ88pVdYZqAtbDw9PlTla7ommygVZQjcfNFXV1pJKRgOCLs-YyCjOJHLKL04zYj0X6KsOCTUeqhj7ml96wLZ-g1X9tyOR2WAr2Ctq7wIEHwqhxOLgOSKqm05xH6Vi3E_hekf50oe2jPfKEA", + TParserTickets::ServiceFlag())); + UNIT_ASSERT_EQUAL(TParserTickets::TStrRes({ETicketStatus::InvalidTicketType, + {}, + {}, + {}}), + TParserTickets::ParseStrV3("3:serv:CgYIDRCUkQYQDBgcIgdiYjpzZXNzIghiYjpzZXNzMg:ERmeH_yzC7K_QsoHTyw7llCsyExEz3CoEopPIuivA0ZAtTaFq_Pa0l9Fhhx_NX9WpOp2CPyY5cFc4PXhcO83jCB7-EGvHNxGN-j2NQalERzPiKqkDCO0Q5etLzSzrfTlvMz7sXDvELNBHyA0PkAQnbz4supY0l-0Q6JBYSEF3zOVMjjE-HeQIFL3ats3_PakaUMWRvgQQ88pVdYZqAtbDw9PlTla7ommygVZQjcfNFXV1pJKRgOCLs-YyCjOJHLKL04zYj0X6KsOCTUeqhj7ml96wLZ-g1X9tyOR2WAr2Ctq7wIEHwqhxOLgOSKqm05xH6Vi3E_hekf50oe2jPfKEA", + TParserTickets::UserFlag())); + UNIT_ASSERT_EQUAL(TParserTickets::TStrRes({ETicketStatus::Malformed, + {}, + {}, + {}}), + TParserTickets::ParseStrV3("3:serv::ERmeH_yzC7K_QsoHTyw7llCsyExEz3CoEopPIuivA0ZAtTaFq_Pa0l9Fhhx_NX9WpOp2CPyY5cFc4PXhcO83jCB7-EGvHNxGN-j2NQalERzPiKqkDCO0Q5etLzSzrfTlvMz7sXDvELNBHyA0PkAQnbz4supY0l-0Q6JBYSEF3zOVMjjE-HeQIFL3ats3_PakaUMWRvgQQ88pVdYZqAtbDw9PlTla7ommygVZQjcfNFXV1pJKRgOCLs-YyCjOJHLKL04zYj0X6KsOCTUeqhj7ml96wLZ-g1X9tyOR2WAr2Ctq7wIEHwqhxOLgOSKqm05xH6Vi3E_hekf50oe2jPfKEA", + TParserTickets::ServiceFlag())); + UNIT_ASSERT_EQUAL(TParserTickets::TStrRes({ETicketStatus::Malformed, + {}, + {}, + {}}), + TParserTickets::ParseStrV3("3:serv:CgYIDRCUkQYQDBgcIgdiYjpzZXNzIghiYjpzZXNzMg:", + TParserTickets::ServiceFlag())); + UNIT_ASSERT_EQUAL(TParserTickets::TStrRes({ETicketStatus::Malformed, + {}, + {}, + {}}), + TParserTickets::ParseStrV3("3:serv:CgYIDRCUkQYQDBgcIgdiYjpzZXNzIghiYjpzZXNzMg:ERmeH_yzC7K_QsoHTyw7llCsyExEz3CoEopPIuivA0ZAtTaFq_Pa0l9Fhhx_NX9WpOp2CPyY5cFc4PXhcO83jCB7-EGvHNxGN-j2NQalERzPiKqkDCO0Q5etLzSzrfTlvMz7sXDvELNBHyA0PkAQnbz4supY0l-0Q6JBYSEF3zOVMjjE-HeQIFL3ats3_PakaUMWRvgQQ88pVdYZqAtbDw9PlTla7ommygVZQjcfNFXV1pJKRgOCLs-YyCjOJHLKL04zYj0X6KsOCTUeqhj7ml96wLZ-g1X9tyOR2WAr2Ctq7wIEHwqhxOLgOSKqm05xH6Vi3E_hekf50oe2jPfKEA:asd", + TParserTickets::ServiceFlag())); + UNIT_ASSERT_EQUAL(TParserTickets::TStrRes({ETicketStatus::Malformed, + {}, + {}, + {}}), + TParserTickets::ParseStrV3("3:serv:CgY+-*/IDRCUkQYQDBgcIgdiYjpzZXNzIghiYjpzZXNzMg:ERmeH_yzC7K_QsoHTyw7llCsyExEz3CoEopPIuivA0ZAtTaFq_Pa0l9Fhhx_NX9WpOp2CPyY5cFc4PXhcO83jCB7-EGvHNxGN-j2NQalERzPiKqkDCO0Q5etLzSzrfTlvMz7sXDvELNBHyA0PkAQnbz4supY0l-0Q6JBYSEF3zOVMjjE-HeQIFL3ats3_PakaUMWRvgQQ88pVdYZqAtbDw9PlTla7ommygVZQjcfNFXV1pJKRgOCLs-YyCjOJHLKL04zYj0X6KsOCTUeqhj7ml96wLZ-g1X9tyOR2WAr2Ctq7wIEHwqhxOLgOSKqm05xH6Vi3E_hekf50oe2jPfKEA", + TParserTickets::ServiceFlag())); + UNIT_ASSERT_EQUAL(TParserTickets::TStrRes({ETicketStatus::Malformed, + {}, + {}, + {}}), + TParserTickets::ParseStrV3("3:serv:CgYIDRCUkQYQDBgcIgdiYjpzZXNzIghiYjpzZXNzMg:ERme/*-+H_yzC7K_QsoHTyw7llCsyExEz3CoEopPIuivA0ZAtTaFq_Pa0l9Fhhx_NX9WpOp2CPyY5cFc4PXhcO83jCB7-EGvHNxGN-j2NQalERzPiKqkDCO0Q5etLzSzrfTlvMz7sXDvELNBHyA0PkAQnbz4supY0l-0Q6JBYSEF3zOVMjjE-HeQIFL3ats3_PakaUMWRvgQQ88pVdYZqAtbDw9PlTla7ommygVZQjcfNFXV1pJKRgOCLs-YyCjOJHLKL04zYj0X6KsOCTUeqhj7ml96wLZ-g1X9tyOR2WAr2Ctq7wIEHwqhxOLgOSKqm05xH6Vi3E_hekf50oe2jPfKEA", + TParserTickets::ServiceFlag())); + UNIT_ASSERT_EQUAL(TParserTickets::TStrRes({ETicketStatus::Malformed, + {}, + {}, + {}}), + TParserTickets::ParseStrV3("", + TParserTickets::ServiceFlag())); + UNIT_ASSERT_EQUAL(TParserTickets::TStrRes({ETicketStatus::Malformed, + {}, + {}, + {}}), + TParserTickets::ParseStrV3("'", + TParserTickets::ServiceFlag())); + + // Invalid proto + UNIT_ASSERT_EQUAL(TParserTickets::TStrRes({ETicketStatus::Ok, + NUtils::Base64url2bin("YIDRCUkQYBgcIgdiYjpzZXNzIghiYjpzZXNzMg"), + NUtils::Base64url2bin("ERmeH_yzC7K_QsoHTyw7llCsyExEz3CoEopPIuivA0ZAtTaFq_Pa0l9Fhhx_NX9WpOp2CPyY5cFc4PXhcO83jCB7-EGvHNxGN-j2NQalERzPiKqkDCO0Q5etLzSzrfTlvMz7sXDvELNBHyA0PkAQnbz4supY0l-0Q6JBYSEF3zOVMjjE-HeQIFL3ats3_PakaUMWRvgQQ88pVdYZqAtbDw9PlTla7ommygVZQjcfNFXV1pJKRgOCLs-YyCjOJHLKL04zYj0X6KsOCTUeqhj7ml96wLZ-g1X9tyOR2WAr2Ctq7wIEHwqhxOLgOSKqm05xH6Vi3E_hekf50oe2jPfKEA"), + "3:serv:YIDRCUkQYBgcIgdiYjpzZXNzIghiYjpzZXNzMg:"}), + TParserTickets::ParseStrV3("3:serv:YIDRCUkQYBgcIgdiYjpzZXNzIghiYjpzZXNzMg:ERmeH_yzC7K_QsoHTyw7llCsyExEz3CoEopPIuivA0ZAtTaFq_Pa0l9Fhhx_NX9WpOp2CPyY5cFc4PXhcO83jCB7-EGvHNxGN-j2NQalERzPiKqkDCO0Q5etLzSzrfTlvMz7sXDvELNBHyA0PkAQnbz4supY0l-0Q6JBYSEF3zOVMjjE-HeQIFL3ats3_PakaUMWRvgQQ88pVdYZqAtbDw9PlTla7ommygVZQjcfNFXV1pJKRgOCLs-YyCjOJHLKL04zYj0X6KsOCTUeqhj7ml96wLZ-g1X9tyOR2WAr2Ctq7wIEHwqhxOLgOSKqm05xH6Vi3E_hekf50oe2jPfKEA", + TParserTickets::ServiceFlag())); + } + + Y_UNIT_TEST(TicketsV3) { + NRw::TPublicKeys pub; + + UNIT_ASSERT_EQUAL(ETicketStatus::Malformed, + TParserTickets::ParseV3("3:serv:CgYIDRCUkQYQDBgcIgdiYjpzZXNzIghiYjpzZXNzMg:ERme/*-+H_yzC7K_QsoHTyw7llCsyExEz3CoEopPIuivA0ZAtTaFq_Pa0l9Fhhx_NX9WpOp2CPyY5cFc4PXhcO83jCB7-EGvHNxGN-j2NQalERzPiKqkDCO0Q5etLzSzrfTlvMz7sXDvELNBHyA0PkAQnbz4supY0l-0Q6JBYSEF3zOVMjjE-HeQIFL3ats3_PakaUMWRvgQQ88pVdYZqAtbDw9PlTla7ommygVZQjcfNFXV1pJKRgOCLs-YyCjOJHLKL04zYj0X6KsOCTUeqhj7ml96wLZ-g1X9tyOR2WAr2Ctq7wIEHwqhxOLgOSKqm05xH6Vi3E_hekf50oe2jPfKEA", + pub, + TParserTickets::ServiceFlag()) + .Status); + + // Invalid proto + UNIT_ASSERT_EQUAL(ETicketStatus::Malformed, + TParserTickets::ParseV3("3:serv:YIDRCUkQYBgcIgdiYjpzZXNzIghiYjpzZXNzMg:ERmeH_yzC7K_QsoHTyw7llCsyExEz3CoEopPIuivA0ZAtTaFq_Pa0l9Fhhx_NX9WpOp2CPyY5cFc4PXhcO83jCB7-EGvHNxGN-j2NQalERzPiKqkDCO0Q5etLzSzrfTlvMz7sXDvELNBHyA0PkAQnbz4supY0l-0Q6JBYSEF3zOVMjjE-HeQIFL3ats3_PakaUMWRvgQQ88pVdYZqAtbDw9PlTla7ommygVZQjcfNFXV1pJKRgOCLs-YyCjOJHLKL04zYj0X6KsOCTUeqhj7ml96wLZ-g1X9tyOR2WAr2Ctq7wIEHwqhxOLgOSKqm05xH6Vi3E_hekf50oe2jPfKEA", + pub, + TParserTickets::ServiceFlag()) + .Status); + + // Expire time == 100500 + UNIT_ASSERT_EQUAL(ETicketStatus::Expired, + TParserTickets::ParseV3("3:serv:CBAQlJEGIhcIDBAcGgdiYjpzZXNzGghiYjpzZXNzMg:HEzPbsjULegBvgX3nqwFX0GfVhESmN1kEWyeT7U03KAR-sQnNYgm6IuN-b9-lQYQKAJSW6p8ffyucC1yDrWSWRxXVzHJUxAVW4hnbiFDtXrurnEdpMK3izKbmTY25PJ4vH3_TkRXk-_oSAE8RvIFKXlh-aw1tezbXBUpJKvyJ0w", + pub, + TParserTickets::ServiceFlag()) + .Status); + + UNIT_ASSERT_EQUAL(ETicketStatus::MissingKey, + TParserTickets::ParseV3("3:serv:CBAQ__________9_IhcIDBAcGgdiYjpzZXNzGghiYjpzZXNzMg:OKjKEbygehEZWH0XEeLzvf0q0aS0VvSk_CKSXGdpqxPbE4RzU70jeM-X9rXVpbYjt76VgBLlBpumJdyiclulfGPDPiL8nwJuu8AnWIR_o-QqyXbsloo2_syE6w2aYw2Yw_5_qjnipYdxGUWegHAGCj3yeMde6O2BmNZ0OCfg6qU", + pub, + TParserTickets::ServiceFlag()) + .Status); + + pub.emplace(16, NRw::TRwPublicKey(NUtils::Base64url2bin("MIGEAoGBALhrihbf3EpjDQS2sCQHazoFgN0nBbE9eesnnFTfzQELXb2gnJU9enmV_aDqaHKjgtLIPpCgn40lHrn5k6mvH5OdedyI6cCzE-N-GFp3nAq0NDJyMe0fhtIRD__CbT0ulcvkeow65ubXWfw6dBC2gR_34rdMe_L_TGRLMWjDULbN"))); + UNIT_ASSERT_EQUAL(ETicketStatus::SignBroken, + TParserTickets::ParseV3("3:serv:CBAQ__________9_IhcIDBAcGgdiYjpzZXNzGghiYjpzZXNzMa:OKjKEbygehEZWH0XEeLzvf0q0aS0VvSk_CKSXGdpqxPbE4RzU70jeM-X9rXVpbYjt76VgBLlBpumJdyiclulfGPDPiL8nwJuu8AnWIR_o-QqyXbsloo2_syE6w2aYw2Yw_5_qjnipYdxGUWegHAGCj3yeMde6O2BmNZ0OCfg6qU", + pub, + TParserTickets::ServiceFlag()) + .Status); + UNIT_ASSERT_EQUAL(ETicketStatus::SignBroken, + TParserTickets::ParseV3("3:serv:CBAQ__________9_IhcIDBAcGgdiYjpzZXNzGghiYjpzZXNzMg:OKjKEbygehEZWH0XEeLzvf0q0aS0VvSk_CKSXGdpqxPbE4RzU70jeM-X9rXVpbYjt76VgBLlBpumJdyiclulfGPDPiL8nwJuu8AnWIR_o-QqyXbsloo2_syE6w2aYw2Yw_5_qjnipYdxGUWegHAGCj3yeMde6O2BmNZ0OCfg6qa", + pub, + TParserTickets::ServiceFlag()) + .Status); + UNIT_ASSERT_EQUAL(ETicketStatus::SignBroken, + TParserTickets::ParseV3("3:serv:CBAQ__________9_IhcIDBAcGgdiYjpzZXNzGghiYjpzZXNzMg:EbygehEZWH0XEeLzvf0q0aS0VvSk_CKSXGdpqxPbE4RzU70jeM-X9rXVpbYjt76VgBLlBpumJdyiclulfGPDPiL8nwJuu8AnWIR_o-QqyXbsloo2_syE6w2aYw2Yw_5_qjnipYdxGUWegHAGCj3yeMde6O2BmNZ0OCfg6qU", + pub, + TParserTickets::ServiceFlag()) + .Status); + + UNIT_ASSERT_EQUAL(ETicketStatus::Ok, + TParserTickets::ParseV3("3:serv:CBAQ__________9_IhcIDBAcGgdiYjpzZXNzGghiYjpzZXNzMg:OKjKEbygehEZWH0XEeLzvf0q0aS0VvSk_CKSXGdpqxPbE4RzU70jeM-X9rXVpbYjt76VgBLlBpumJdyiclulfGPDPiL8nwJuu8AnWIR_o-QqyXbsloo2_syE6w2aYw2Yw_5_qjnipYdxGUWegHAGCj3yeMde6O2BmNZ0OCfg6qU", + pub, + TParserTickets::ServiceFlag()) + .Status); + } +} diff --git a/library/cpp/tvmauth/src/ut/public_ut.cpp b/library/cpp/tvmauth/src/ut/public_ut.cpp new file mode 100644 index 0000000000..74a483d57b --- /dev/null +++ b/library/cpp/tvmauth/src/ut/public_ut.cpp @@ -0,0 +1,290 @@ +// DO_NOT_STYLE +#include <library/cpp/tvmauth/src/service_impl.h> +#include <library/cpp/tvmauth/src/user_impl.h> + +#include <library/cpp/tvmauth/exception.h> +#include <library/cpp/tvmauth/ticket_status.h> +#include <library/cpp/tvmauth/unittest.h> + +#include <library/cpp/testing/unittest/registar.h> + +using namespace NTvmAuth; + +Y_UNIT_TEST_SUITE(CommonPublicInterfaceTestSuite){ + Y_UNIT_TEST(StatusTest){ + UNIT_ASSERT_VALUES_EQUAL("OK", + StatusToString(ETicketStatus::Ok)); + UNIT_ASSERT_VALUES_EQUAL("Expired ticket", + StatusToString(ETicketStatus::Expired)); + UNIT_ASSERT_VALUES_EQUAL("Invalid BlackBox environment", + StatusToString(ETicketStatus::InvalidBlackboxEnv)); + UNIT_ASSERT_VALUES_EQUAL("Invalid ticket destination", + StatusToString(ETicketStatus::InvalidDst)); + UNIT_ASSERT_VALUES_EQUAL("Invalid ticket type", + StatusToString(ETicketStatus::InvalidTicketType)); + UNIT_ASSERT_VALUES_EQUAL("Malformed ticket", + StatusToString(ETicketStatus::Malformed)); + UNIT_ASSERT_VALUES_EQUAL("Invalid ticket signature", + StatusToString(ETicketStatus::SignBroken)); + UNIT_ASSERT_VALUES_EQUAL("Context does not have required key to check ticket: public keys are too old", + StatusToString(ETicketStatus::MissingKey)); + UNIT_ASSERT_VALUES_EQUAL("Unsupported ticket version", + StatusToString(ETicketStatus::UnsupportedVersion)); + } +} + +Y_UNIT_TEST_SUITE(PublicInterfaceServiceTestSuite) { + static const TString EMPTY_TVM_KEYS = "1:CpgCCpMCCAEQABqIAjCCAQQCggEAcLEXeH67FQESFUn4_7wnX7wN0PUrBoUsm3QQ4W5vC-qz6sXaEjSwnTV8w1o-z6X9KPLlhzMQvuS38NCNfK4uvJ4Zvfp3YsXJ25-rYtbnrYJHNvHohD-kPCCw_yZpMp21JdWigzQGuV7CtrxUhF-NNrsnUaJrE5-OpEWNt4X6nCItKIYeVcSK6XJUbEWbrNCRbvkSc4ak2ymFeMuHYJVjxh4eQbk7_ZPzodP0WvF6eUYrYeb42imVEOR8ofVLQWE5DVnb1z_TqZm4i1XkS7jMwZuBxBRw8DGdYei0lT_sAf7KST2jC0590NySB3vsBgWEVs1OdUUWA6r-Dvx9dsOQtSCVkQYQAAqZAgqUAggCEAAaiQIwggEFAoIBAQDhEBM5-6YsPWfogKtbluJoCX1WV2KdzOaQ0-OlRbBzeCzw-eQKu12c8WakHBbeCMd1I1TU64SDkDorWjXGIa_2xT6N3zzNAE50roTbPCcmeQrps26woTYfYIuqDdoxYKZNr0lvNLLW47vBr7EKqo1S4KSj7aXK_XYeEvUgIgf3nVIcNrio7VTnFmGGVQCepaL1Hi1gN4yIXjVZ06PBPZ-DxSRu6xOGbFrfKMJeMPs7KOyE-26Q3xOXdTIa1X-zYIucTd_bxUCL4BVbwW2AvbbFsaG7ISmVdGu0XUTmhXs1KrEfUVLRJhE4Dx99hAZXm1_HlYMUeJcMQ_oHOhV94ENFIJaRBhACCpYBCpEBCAMQABqGATCBgwKBgF9t2YJGAJkRRFq6fWhi3m1TFW1UOE0f6ZrfYhHAkpqGlKlh0QVfeTNPpeJhi75xXzCe6oReRUm-0DbqDNhTShC7uGUv1INYnRBQWH6E-5Fc5XrbDFSuGQw2EYjNfHy_HefHJXxQKAqPvxBDKMKkHgV58WtM6rC8jRi9sdX_ig2NIJeRBhABCpYBCpEBCAQQABqGATCBgwKBgGB4d6eLGUBv-Q6EPLehC4S-yuE2HB-_rJ7WkeYwyp-xIPolPrd-PQme2utHB4ZgpXHIu_OFksDe_0bPgZniNRSVRbl7W49DgS5Ya3kMfrYB4DnF5Fta5tn1oV6EwxYD4JONpFTenOJALPGTPawxXEfon_peiHOSBuQMu3_Vn-l1IJiRBhADCpcBCpIBCAUQABqHATCBhAKBgQCTJMKIfmfeZpaI7Q9rnsc29gdWawK7TnpVKRHws1iY7EUlYROeVcMdAwEqVM6f8BVCKLGgzQ7Gar_uuxfUGKwqEQzoppDraw4F75J464-7D5f6_oJQuGIBHZxqbMONtLjBCXRUhQW5szBLmTQ_R3qaJb5vf-h0APZfkYhq1cTttSCZkQYQBAqWAQqRAQgLEAAahgEwgYMCgYBvvGVH_M2H8qxxv94yaDYUTWbRnJ1uiIYc59KIQlfFimMPhSS7x2tqUa2-hI55JiII0Xym6GNkwLhyc1xtWChpVuIdSnbvttbrt4weDMLHqTwNOF6qAsVKGKT1Yh8yf-qb-DSmicgvFc74mBQm_6gAY1iQsf33YX8578ClhKBWHSCVkQYQAAqXAQqSAQgMEAAahwEwgYQCgYEAkuzFcd5TJu7lYWYe2hQLFfUWIIj91BvQQLa_Thln4YtGCO8gG1KJqJm-YlmJOWQG0B7H_5RVhxUxV9KpmFnsDVkzUFKOsCBaYGXc12xPVioawUlAwp5qp3QQtZyx_se97YIoLzuLr46UkLcLnkIrp-Jo46QzYi_QHq45WTm8MQ0glpEGEAIKlwEKkgEIDRAAGocBMIGEAoGBAIUzbxOknXf_rNt17_ir8JlWvrtnCWsQd1MAnl5mgArvavDtKeBYHzi5_Ak7DHlLzuA6YE8W175FxLFKpN2hkz-l-M7ltUSd8N1BvJRhK4t6WffWfC_1wPyoAbeSN2Yb1jygtZJQ8wGoXHcJQUXiMit3eFNyylwsJFj1gzAR4JCdIJeRBhABCpYBCpEBCA4QABqGATCBgwKBgFMcbEpl9ukVR6AO_R6sMyiU11I8b8MBSUCEC15iKsrVO8v_m47_TRRjWPYtQ9eZ7o1ocNJHaGUU7qqInFqtFaVnIceP6NmCsXhjs3MLrWPS8IRAy4Zf4FKmGOx3N9O2vemjUygZ9vUiSkULdVrecinRaT8JQ5RG4bUMY04XGIwFIJiRBhADCpYBCpEBCA8QABqGATCBgwKBgGpCkW-NR3li8GlRvqpq2YZGSIgm_PTyDI2Zwfw69grsBmPpVFW48Vw7xoMN35zcrojEpialB_uQzlpLYOvsMl634CRIuj-n1QE3-gaZTTTE8mg-AR4mcxnTKThPnRQpbuOlYAnriwiasWiQEMbGjq_HmWioYYxFo9USlklQn4-9IJmRBhAE"; + static const TString EXPIRED_SERVICE_TICKET = "3:serv:CBAQACIZCOUBEBwaCGJiOnNlc3MxGghiYjpzZXNzMg:IwfMNJYEqStY_SixwqJnyHOMCPR7-3HHk4uylB2oVRkthtezq-OOA7QizDvx7VABLs_iTlXuD1r5IjufNei_EiV145eaa3HIg4xCdJXCojMexf2UYJz8mF2b0YzFAy6_KWagU7xo13CyKAqzJuQf5MJcSUf0ecY9hVh36cJ51aw"; + static const TString MALFORMED_TVM_KEYS = "1:CpgCCpMCCAEQABqIAjCCAQQCggEAcLEXeH67FQESFUn4_7wnX7wN0PUrBoUsm3QQ4W5vC-qz6sXaEjSwnTV8w1o-z6X9KPLlhzMQvuS38NCNfK4uvJ4Zvfp3YsXJ25-rYtbnrYJHNvHohD-kPCCw_yZpMp21JdWigzQGuV7CtrxUhF-NNrsnUaJrE5-OpEWNt4X6nCItKIYeVcSK6XJUbEWbrNCRbvkSc4ak2ymFeMuHYJVjxh4eQbk7_ZPzodP0WvF6eUYrYeb42imVEOR8ofVLQWE5DVnb1z_TqZm4i1XkS7jMwZuBxBRw8DGdYei0lT_sAf7KST2jC0590NySB3vsBgWEVs1OdUUWA6r-Dvx9dsOQtSCVkQYQAAqZAgqUAggCEAAaiQIwggEFAoIBAQDhEBM5-6YsPWfogKtbluJoCX1WV2KdzOaQ0-OlRbBzeCzw-eQKu12c8WakHBbeCMd1I1TU64SDkDorWjXGIa_2xT6N3zzNAE50roTbPCcmeQrps26woTYfYIuqDdoxYKZNr0lvNLLW47vBr7EKqo1S4KSj7aXK_XYeEvUgIgf3nVIcNrio7VTnFmGGVQCepaL1Hi1gN4yIXjVZ06PBPZ-DxSRu6xOGbFrfKMJeMPs7KOyE-26Q3xOXdTIa1X-zYIucTd_bxUCL4BVbwW2AvbbFsaG7ISmVdGu0XUTmhXs1KrEfUVLRJhE4Dx99hAZXm1_HlYMUeJcMQ_oHOhV94ENFIJaRBhACCpYBCpEBCAMQABqGATCBgwKBgF9t2YJGAJkRRFq6fWhi3m1TFW1UOE0f6ZrfYhHAkpqGlKlh0QVfeTNPpeJhi75xXzCe6oReRUm-0DbqDNhTShC7uGUv1INYnRBQWH6E-5Fc5XrbDFSuGQw2EYjNfHy_HefHJXxQKAqPvxBDKMKkHgV58WtM6rC8jRi9sdX_ig2NIJeRBhABCpYBCpEBCAQQABqGATCBgwKBgGB4d6eLGUBv-Q6EPLehC4S-yuE2HB-_rJ7WkeYwyp-xIPolPrd-PQme2utHB4ZgpXHIu_OFksDe_0bPgZniNRSVRbl7W49DgS5Ya3kMfrYB4DnF5Fta5tn1oV6EwxYD4JONpFTenOJALPGTPawxXEfon_peiHOSBuQMu3_Vn-l1IJiRBhADCpcBCpIBCAUQABqHATCBhAKBgQCTJMKIfmfeZpaI7Q9rnsc29gdWawK7TnpVKRHws1iY7EUlYROeVcMdAwEqVM6f8BVCKLGgzQ7Gar_uuxfUGKwqEQzoppDraw4F75J464-7D5f6_oJQuGIBHZxqbMONtLjBCXRUhQW5szBLmTQ_R3qaJb5vf-h0APZfkYhq1cTttSCZkQYQBAqWAQqRAQgLEAAahgEwgYMCgYBvvGVH_M2H8qxxv94yaDYUTWbRnJ1uiIYc59KIQlfFimMPhSS7x2tqUa2-hI55JiII0Xym6GNkwLhyc1xtWChpVuIdSnbvttbrt4weDMLHqTwNOF6qAsVKGKT1Yh8yf-qb-DSmicgvFc74mBQm_6gAY1iQsf33YX8578ClhKBWHSCVkQYQAAqXAQqSAQgMEAAahwEwgYQCgYEAkuzFcd5TJu7lYWYe2hQLFfUWIIj91BvQQLa_Thln4YtGCO8gG1KJqJm-YlmJOWQG0B7H_5RVhxUxV9KpmFnsDVkzUFKOsCBaYGXc12xPVioawUlAwp5qp3QQtZyx_se97YIoLzuLr46UkLcLnkIrp-Jo46QzYi_QHq45WTm8MQ0glpEGEAIKlwEKkgEIDRAAGocBMIGEAoGBAIUzbxOknXf_rNt17_ir8JlWvrtnCWsQd1MAnl5mgArvavDtKeBYHzi5_Ak7DHlLzuA6YE8W175FxLFKpN2hkz-l-M7ltUSd8N1BvJRhK4t6WffWfC_1wPyoAbeSN2Yb1jygtZJQ8wGoXHcJQUXiMit3eFNyylwsJFj1gzAR4JCdIJeRBhABCpYBCpEBCA4QABqGATCBgwKBgFMcbEpl9ukVR6AO_R6sMyiU11I8b8MBSUCEC15iKsrVO8v_m47_TRRjWPYtQ9eZ7o1ocNJHaGUU7qqInFqtFaVnIceP6NmCsXhjs3MLrWPS8IRAy4Zf4FKmGOx3N9O2vemjUygZ9vUiSkULdVrecinRaT8JQ5RG4bUMY04XGIwFIJiRBhADCpYBCpEBCA8QABqGATCBgwKBgGpCkW-NR3li8GlRvqpq2YZGSIgm_PTyDI2Zwfw69grsBmPpVFW48Vw7xoMN35zcrojEpialB_uQzlpLYOvsMl634CRIuj-n1QE3-gaZTTTE8mg-AR4mcxnTKThPnRQpbuOlYAnriwiasWiQEMbGjq_HmWioYYxFo9USlklQn4-9IJmRBhAEEpUBCpIBCAYQABqHATCBhAKBgQCoZkFGm9oLTqjeXZAq6j5S6i7K20V0lNdBBLqfmFBIRuTkYxhs4vUYnWjZrKRAd5bp6_py0csmFmpl_5Yh0b-2pdo_E5PNP7LGRzKyKSiFddyykKKzVOazH8YYldDAfE8Z5HoS9e48an5JsPg0jr-TPu34DnJq3yv2a6dqiKL9zSCakQYSlQEKkgEIEBAAGocBMIGEAoGBALhrihbf3EpjDQS2sCQHazoFgN0nBbE9eesnnFTfzQELXb2gnJU9enmV_aDqaHKjgtLIPpCgn40lHrn5k6mvH5OdedyI6cCzE-N-GFp3nAq0NDJyMe0fhtIRD__CbT0ulcvkeow65ubXWfw6dBC2gR_34rdMe_L_TGRLMWjDULbNIJ"; + static const TString MALFORMED_TVM_SECRET = "adcvxcv./-+"; + static const TTvmId NOT_OUR_ID = 27; + static const TTvmId OUR_ID = 28; + static const TString SECRET = "GRMJrKnj4fOVnvOqe-WyD1"; + static const TString SERVICE_TICKET_PROTOBUF = "CBAQ__________9_IhkI5QEQHBoIYmI6c2VzczEaCGJiOnNlc3My"; + static const TTvmId SRC_ID = 229; + static const TString UNSUPPORTED_VERSION_SERVICE_TICKET = "2:serv:CBAQ__________9_IhkI5QEQHBoIYmI6c2VzczEaCGJiOnNlc3My:WUPx1cTf05fjD1exB35T5j2DCHWH1YaLJon_a4rN-D7JfXHK1Ai4wM4uSfboHD9xmGQH7extqtlEk1tCTCGm5qbRVloJwWzCZBXo3zKX6i1oBYP_89WcjCNPVe1e8jwGdLsnu6PpxL5cn0xCksiStILH5UmDR6xfkJdnmMG94o8"; + static const TString VALID_SERVICE_TICKET_1 = "3:serv:CBAQ__________9_IhkI5QEQHBoIYmI6c2VzczEaCGJiOnNlc3My:WUPx1cTf05fjD1exB35T5j2DCHWH1YaLJon_a4rN-D7JfXHK1Ai4wM4uSfboHD9xmGQH7extqtlEk1tCTCGm5qbRVloJwWzCZBXo3zKX6i1oBYP_89WcjCNPVe1e8jwGdLsnu6PpxL5cn0xCksiStILH5UmDR6xfkJdnmMG94o8"; + static const TString VALID_SERVICE_TICKET_2 = "3:serv:CBAQ__________9_IskICOUBEBwaCGJiOnNlc3MxGgliYjpzZXNzMTAaCmJiOnNlc3MxMDAaCWJiOnNlc3MxMRoJYmI6c2VzczEyGgliYjpzZXNzMTMaCWJiOnNlc3MxNBoJYmI6c2VzczE1GgliYjpzZXNzMTYaCWJiOnNlc3MxNxoJYmI6c2VzczE4GgliYjpzZXNzMTkaCGJiOnNlc3MyGgliYjpzZXNzMjAaCWJiOnNlc3MyMRoJYmI6c2VzczIyGgliYjpzZXNzMjMaCWJiOnNlc3MyNBoJYmI6c2VzczI1GgliYjpzZXNzMjYaCWJiOnNlc3MyNxoJYmI6c2VzczI4GgliYjpzZXNzMjkaCGJiOnNlc3MzGgliYjpzZXNzMzAaCWJiOnNlc3MzMRoJYmI6c2VzczMyGgliYjpzZXNzMzMaCWJiOnNlc3MzNBoJYmI6c2VzczM1GgliYjpzZXNzMzYaCWJiOnNlc3MzNxoJYmI6c2VzczM4GgliYjpzZXNzMzkaCGJiOnNlc3M0GgliYjpzZXNzNDAaCWJiOnNlc3M0MRoJYmI6c2VzczQyGgliYjpzZXNzNDMaCWJiOnNlc3M0NBoJYmI6c2VzczQ1GgliYjpzZXNzNDYaCWJiOnNlc3M0NxoJYmI6c2VzczQ4GgliYjpzZXNzNDkaCGJiOnNlc3M1GgliYjpzZXNzNTAaCWJiOnNlc3M1MRoJYmI6c2VzczUyGgliYjpzZXNzNTMaCWJiOnNlc3M1NBoJYmI6c2VzczU1GgliYjpzZXNzNTYaCWJiOnNlc3M1NxoJYmI6c2VzczU4GgliYjpzZXNzNTkaCGJiOnNlc3M2GgliYjpzZXNzNjAaCWJiOnNlc3M2MRoJYmI6c2VzczYyGgliYjpzZXNzNjMaCWJiOnNlc3M2NBoJYmI6c2VzczY1GgliYjpzZXNzNjYaCWJiOnNlc3M2NxoJYmI6c2VzczY4GgliYjpzZXNzNjkaCGJiOnNlc3M3GgliYjpzZXNzNzAaCWJiOnNlc3M3MRoJYmI6c2VzczcyGgliYjpzZXNzNzMaCWJiOnNlc3M3NBoJYmI6c2Vzczc1GgliYjpzZXNzNzYaCWJiOnNlc3M3NxoJYmI6c2Vzczc4GgliYjpzZXNzNzkaCGJiOnNlc3M4GgliYjpzZXNzODAaCWJiOnNlc3M4MRoJYmI6c2VzczgyGgliYjpzZXNzODMaCWJiOnNlc3M4NBoJYmI6c2Vzczg1GgliYjpzZXNzODYaCWJiOnNlc3M4NxoJYmI6c2Vzczg4GgliYjpzZXNzODkaCGJiOnNlc3M5GgliYjpzZXNzOTAaCWJiOnNlc3M5MRoJYmI6c2VzczkyGgliYjpzZXNzOTMaCWJiOnNlc3M5NBoJYmI6c2Vzczk1GgliYjpzZXNzOTYaCWJiOnNlc3M5NxoJYmI6c2Vzczk4GgliYjpzZXNzOTk:JYmABAVLM6y7_T4n1pRcwBfwDfzMV4JJ3cpbEG617zdGgKRZwL7MalsYn5bq1F2ibujMrsF9nzZf8l4s_e-Ivjkz_xu4KMzSp-pUh9V7XIF_smj0WHYpv6gOvWNuK8uIvlZTTKwtQX0qZOL9m-MEeZiHoQPKZGCfJ_qxMUp-J8I"; + static const TString VALID_SERVICE_TICKET_3 = "3:serv:CBAQ__________9_IgUI5QEQHA:Sd6tmA1CNy2Nf7XevC3x7zr2DrGNRmcl-TxUsDtDW2xI3YXyCxBltWeg0-KtDlqyYuPOP5Jd_-XXNA12KlOPnNzrz3jm-5z8uQl6CjCcrVHUHJ75pGC8r9UOlS8cOgeXQB5dYP-fOWyo5CNadlozx1S2meCIxncbQRV1kCBi4KU"; + + Y_UNIT_TEST(BlackboxTvmIdTest) { + UNIT_ASSERT_VALUES_EQUAL("222", NBlackboxTvmId::Prod); + UNIT_ASSERT_VALUES_EQUAL("224", NBlackboxTvmId::Test); + UNIT_ASSERT_VALUES_EQUAL("223", NBlackboxTvmId::ProdYateam); + UNIT_ASSERT_VALUES_EQUAL("225", NBlackboxTvmId::TestYateam); + UNIT_ASSERT_VALUES_EQUAL("226", NBlackboxTvmId::Stress); + UNIT_ASSERT_VALUES_EQUAL("239", NBlackboxTvmId::Mimino); + } + + Y_UNIT_TEST(Case1Test) { + TServiceContext context1(SECRET, OUR_ID, NUnittest::TVMKNIFE_PUBLIC_KEYS); + TServiceContext context2 = std::move(context1); + TServiceContext context3(std::move(context2)); + + TCheckedServiceTicket checkedTicket1 = context3.Check(VALID_SERVICE_TICKET_1); + UNIT_ASSERT_EQUAL(ETicketStatus::Ok, checkedTicket1.GetStatus()); + TCheckedServiceTicket checkedTicket2 = std::move(checkedTicket1); + TCheckedServiceTicket checkedTicket3(std::move(checkedTicket2)); + UNIT_ASSERT_EQUAL(ETicketStatus::Ok, checkedTicket3.GetStatus()); + } + + Y_UNIT_TEST(ContextExceptionsTest) { + UNIT_ASSERT_EXCEPTION(TServiceContext(SECRET, OUR_ID, MALFORMED_TVM_KEYS), TMalformedTvmKeysException); + UNIT_ASSERT_EXCEPTION(TServiceContext(SECRET, OUR_ID, EMPTY_TVM_KEYS), TEmptyTvmKeysException); + UNIT_ASSERT_EXCEPTION(TServiceContext(MALFORMED_TVM_SECRET, OUR_ID, NUnittest::TVMKNIFE_PUBLIC_KEYS), TMalformedTvmSecretException); + } + + Y_UNIT_TEST(ContextSignTest) { + TServiceContext context(SECRET, OUR_ID, NUnittest::TVMKNIFE_PUBLIC_KEYS); + UNIT_ASSERT_VALUES_EQUAL( + "NsPTYak4Cfk-4vgau5lab3W4GPiTtb2etuj3y4MDPrk", + context.SignCgiParamsForTvm(IntToString<10>(std::numeric_limits<time_t>::max()), "13,28", "")); + } + + Y_UNIT_TEST(ContextSignExceptionTest) { + TServiceContext context = TServiceContext::CheckingFactory(OUR_ID, NUnittest::TVMKNIFE_PUBLIC_KEYS); + UNIT_ASSERT_EXCEPTION( + context.SignCgiParamsForTvm(IntToString<10>(std::numeric_limits<time_t>::max()), "13,28", ""), + TMalformedTvmSecretException + ); + + context = TServiceContext::SigningFactory(SECRET); + UNIT_ASSERT_NO_EXCEPTION( + context.SignCgiParamsForTvm(IntToString<10>(std::numeric_limits<time_t>::max()), "13,28", "") + ); + } + + Y_UNIT_TEST(ContextCheckExceptionTest) { + TServiceContext context = TServiceContext::CheckingFactory(OUR_ID, NUnittest::TVMKNIFE_PUBLIC_KEYS); + UNIT_ASSERT_NO_EXCEPTION( + context.Check("ABCDE") + ); + + context = TServiceContext::SigningFactory(SECRET); + UNIT_ASSERT_EXCEPTION( + context.Check("ABCDE"), + TEmptyTvmKeysException + ); + } + + + Y_UNIT_TEST(ContextTest) { + TServiceContext context1(SECRET, OUR_ID, NUnittest::TVMKNIFE_PUBLIC_KEYS); + TServiceContext context2 = TServiceContext::CheckingFactory(OUR_ID, NUnittest::TVMKNIFE_PUBLIC_KEYS); + } + + Y_UNIT_TEST(Ticket1Test) { + TServiceContext context(SECRET, OUR_ID, NUnittest::TVMKNIFE_PUBLIC_KEYS); + auto checkedTicket = context.Check(VALID_SERVICE_TICKET_1); + UNIT_ASSERT_EQUAL(ETicketStatus::Ok, checkedTicket.GetStatus()); + UNIT_ASSERT_EQUAL(SRC_ID, checkedTicket.GetSrc()); + UNIT_ASSERT_EQUAL("ticket_type=serv;expiration_time=9223372036854775807;src=229;dst=28;scope=bb:sess1;scope=bb:sess2;", checkedTicket.DebugInfo()); + } + + Y_UNIT_TEST(Ticket2Test) { + TServiceContext context(SECRET, OUR_ID, NUnittest::TVMKNIFE_PUBLIC_KEYS); + auto checkedTicket = context.Check(VALID_SERVICE_TICKET_2); + UNIT_ASSERT_EQUAL(ETicketStatus::Ok, checkedTicket.GetStatus()); + UNIT_ASSERT_VALUES_EQUAL("ticket_type=serv;expiration_time=9223372036854775807;src=229;dst=28;scope=bb:sess1;scope=bb:sess10;scope=bb:sess100;scope=bb:sess11;scope=bb:sess12;scope=bb:sess13;scope=bb:sess14;scope=bb:sess15;scope=bb:sess16;scope=bb:sess17;scope=bb:sess18;scope=bb:sess19;scope=bb:sess2;scope=bb:sess20;scope=bb:sess21;scope=bb:sess22;scope=bb:sess23;scope=bb:sess24;scope=bb:sess25;scope=bb:sess26;scope=bb:sess27;scope=bb:sess28;scope=bb:sess29;scope=bb:sess3;scope=bb:sess30;scope=bb:sess31;scope=bb:sess32;scope=bb:sess33;scope=bb:sess34;scope=bb:sess35;scope=bb:sess36;scope=bb:sess37;scope=bb:sess38;scope=bb:sess39;scope=bb:sess4;scope=bb:sess40;scope=bb:sess41;scope=bb:sess42;scope=bb:sess43;scope=bb:sess44;scope=bb:sess45;scope=bb:sess46;scope=bb:sess47;scope=bb:sess48;scope=bb:sess49;scope=bb:sess5;scope=bb:sess50;scope=bb:sess51;scope=bb:sess52;scope=bb:sess53;scope=bb:sess54;scope=bb:sess55;scope=bb:sess56;scope=bb:sess57;scope=bb:sess58;scope=bb:sess59;scope=bb:sess6;scope=bb:sess60;scope=bb:sess61;scope=bb:sess62;scope=bb:sess63;scope=bb:sess64;scope=bb:sess65;scope=bb:sess66;scope=bb:sess67;scope=bb:sess68;scope=bb:sess69;scope=bb:sess7;scope=bb:sess70;scope=bb:sess71;scope=bb:sess72;scope=bb:sess73;scope=bb:sess74;scope=bb:sess75;scope=bb:sess76;scope=bb:sess77;scope=bb:sess78;scope=bb:sess79;scope=bb:sess8;scope=bb:sess80;scope=bb:sess81;scope=bb:sess82;scope=bb:sess83;scope=bb:sess84;scope=bb:sess85;scope=bb:sess86;scope=bb:sess87;scope=bb:sess88;scope=bb:sess89;scope=bb:sess9;scope=bb:sess90;scope=bb:sess91;scope=bb:sess92;scope=bb:sess93;scope=bb:sess94;scope=bb:sess95;scope=bb:sess96;scope=bb:sess97;scope=bb:sess98;scope=bb:sess99;", checkedTicket.DebugInfo()); + } + + Y_UNIT_TEST(Ticket3Test) { + TServiceContext context(SECRET, OUR_ID, NUnittest::TVMKNIFE_PUBLIC_KEYS); + auto checkedTicket = context.Check(VALID_SERVICE_TICKET_3); + UNIT_ASSERT_EQUAL(ETicketStatus::Ok, checkedTicket.GetStatus()); + UNIT_ASSERT_VALUES_EQUAL("ticket_type=serv;expiration_time=9223372036854775807;src=229;dst=28;", checkedTicket.DebugInfo()); + } + + Y_UNIT_TEST(TicketCheckingTest) { + TServiceContext context(SECRET, OUR_ID, NUnittest::TVMKNIFE_PUBLIC_KEYS); + auto ticket = context.Check(VALID_SERVICE_TICKET_1); + UNIT_ASSERT_EQUAL(ETicketStatus::Ok, ticket.GetStatus()); + UNIT_ASSERT_EQUAL(SRC_ID, ticket.GetSrc()); + } + + Y_UNIT_TEST(TicketErrorsTest) { + TServiceContext context(SECRET, NOT_OUR_ID, NUnittest::TVMKNIFE_PUBLIC_KEYS); + auto checkedTicket1 = context.Check(VALID_SERVICE_TICKET_1); + UNIT_ASSERT_EQUAL(ETicketStatus::InvalidDst, checkedTicket1.GetStatus()); + + auto checkedTicket2 = context.Check(UNSUPPORTED_VERSION_SERVICE_TICKET); + UNIT_ASSERT_EQUAL(ETicketStatus::UnsupportedVersion, checkedTicket2.GetStatus()); + + auto checkedTicket3 = context.Check(EXPIRED_SERVICE_TICKET); + UNIT_ASSERT_EQUAL(ETicketStatus::Expired, checkedTicket3.GetStatus()); + } + + Y_UNIT_TEST(TicketExceptionsTest) { + TServiceContext context(SECRET, OUR_ID, NUnittest::TVMKNIFE_PUBLIC_KEYS); + auto checkedTicket = context.Check(EXPIRED_SERVICE_TICKET); + UNIT_ASSERT_EQUAL(ETicketStatus::Expired, checkedTicket.GetStatus()); + + UNIT_ASSERT(!bool(checkedTicket)); + UNIT_ASSERT_EXCEPTION(checkedTicket.GetSrc(), TNotAllowedException); + UNIT_ASSERT_NO_EXCEPTION(bool(checkedTicket)); + UNIT_ASSERT_NO_EXCEPTION(checkedTicket.DebugInfo()); + UNIT_ASSERT_NO_EXCEPTION(checkedTicket.GetStatus()); + } + + Y_UNIT_TEST(RemoveSignatureTest) { + UNIT_ASSERT_VALUES_EQUAL("1:serv:ASDkljbjhsdbfLJHABFJHBslfbsfjs:asdxcvbxcvniueliuweklsvds", + NUtils::RemoveTicketSignature("1:serv:ASDkljbjhsdbfLJHABFJHBslfbsfjs:asdxcvbxcvniueliuweklsvds")); + UNIT_ASSERT_VALUES_EQUAL("2:serv:ASDkljbjhsdbfLJHABFJHBslfbsfjs:asdxcvbxcvniueliuweklsvds", + NUtils::RemoveTicketSignature("2:serv:ASDkljbjhsdbfLJHABFJHBslfbsfjs:asdxcvbxcvniueliuweklsvds")); + UNIT_ASSERT_VALUES_EQUAL("4:serv:ASDkljbjhsdbfLJHABFJHBslfbsfjs:asdxcvbxcvniueliuweklsvds", + NUtils::RemoveTicketSignature("4:serv:ASDkljbjhsdbfLJHABFJHBslfbsfjs:asdxcvbxcvniueliuweklsvds")); + UNIT_ASSERT_VALUES_EQUAL("3.serv.ASDkljbjhsdbfLJHABFJHBslfbsfjs.asdxcvbxcvniueliuweklsvds", + NUtils::RemoveTicketSignature("3.serv.ASDkljbjhsdbfLJHABFJHBslfbsfjs.asdxcvbxcvniueliuweklsvds")); + UNIT_ASSERT_VALUES_EQUAL("3:serv:ASDkljbjhsdbfLJHABFJHBslfbsfjs:", + NUtils::RemoveTicketSignature("3:serv:ASDkljbjhsdbfLJHABFJHBslfbsfjs:asdxcvbxcvniueliuweklsvds")); + UNIT_ASSERT_VALUES_EQUAL("3:serv:ASDkljbjhsdbfLJHABFJHBslfbsfjs:", + NUtils::RemoveTicketSignature("3:serv:ASDkljbjhsdbfLJHABFJHBslfbsfjs:asdxcvbxcvniueliuweklsvds")); + UNIT_ASSERT_VALUES_EQUAL("3:serv:", + NUtils::RemoveTicketSignature("3:serv:ASDkljbjhsdbfLJHABFJHBslfbsfjs.asdxcvbxcvniueliuweklsvds")); + UNIT_ASSERT_VALUES_EQUAL("asdxcbvfgdsgfasdfxczvdsgfxcdvbcbvf", + NUtils::RemoveTicketSignature("asdxcbvfgdsgfasdfxczvdsgfxcdvbcbvf")); + } + + Y_UNIT_TEST(ResetKeysTest) { + TServiceContext context(SECRET, OUR_ID, NUnittest::TVMKNIFE_PUBLIC_KEYS); + TCheckedServiceTicket checkedTicket = context.Check(VALID_SERVICE_TICKET_1); + UNIT_ASSERT_EQUAL(ETicketStatus::Ok, checkedTicket.GetStatus()); + } +} + +Y_UNIT_TEST_SUITE(PublicInterfaceUserTestSuite) { + static const TString EMPTY_TVM_KEYS = "1:EpUBCpIBCAYQABqHATCBhAKBgQCoZkFGm9oLTqjeXZAq6j5S6i7K20V0lNdBBLqfmFBIRuTkYxhs4vUYnWjZrKRAd5bp6_py0csmFmpl_5Yh0b-2pdo_E5PNP7LGRzKyKSiFddyykKKzVOazH8YYldDAfE8Z5HoS9e48an5JsPg0jr-TPu34DnJq3yv2a6dqiKL9zSCakQY"; + static const TString EXPIRED_USER_TICKET = "3:user:CA0QABokCgMIyAMKAgh7EMgDGghiYjpzZXNzMRoIYmI6c2VzczIgEigB:D0CmYVwWg91LDYejjeQ2UP8AeiA_mr1q1CUD_lfJ9zQSEYEOYGDTafg4Um2rwOOvQnsD1JHM4zHyMUJ6Jtp9GAm5pmhbXBBZqaCcJpyxLTEC8a81MhJFCCJRvu_G1FiAgRgB25gI3HIbkvHFUEqAIC_nANy7NFQnbKk2S-EQPGY"; + static const TString MALFORMED_TVM_KEYS = "1:CpgCCpMCCAEQABqIAjCCAQQCggEAcLEXeH67FQESFUn4_7wnX7wN0PUrBoUsm3QQ4W5vC-qz6sXaEjSwnTV8w1o-z6X9KPLlhzMQvuS38NCNfK4uvJ4Zvfp3YsXJ25-rYtbnrYJHNvHohD-kPCCw_yZpMp21JdWigzQGuV7CtrxUhF-NNrsnUaJrE5-OpEWNt4X6nCItKIYeVcSK6XJUbEWbrNCRbvkSc4ak2ymFeMuHYJVjxh4eQbk7_ZPzodP0WvF6eUYrYeb42imVEOR8ofVLQWE5DVnb1z_TqZm4i1XkS7jMwZuBxBRw8DGdYei0lT_sAf7KST2jC0590NySB3vsBgWEVs1OdUUWA6r-Dvx9dsOQtSCVkQYQAAqZAgqUAggCEAAaiQIwggEFAoIBAQDhEBM5-6YsPWfogKtbluJoCX1WV2KdzOaQ0-OlRbBzeCzw-eQKu12c8WakHBbeCMd1I1TU64SDkDorWjXGIa_2xT6N3zzNAE50roTbPCcmeQrps26woTYfYIuqDdoxYKZNr0lvNLLW47vBr7EKqo1S4KSj7aXK_XYeEvUgIgf3nVIcNrio7VTnFmGGVQCepaL1Hi1gN4yIXjVZ06PBPZ-DxSRu6xOGbFrfKMJeMPs7KOyE-26Q3xOXdTIa1X-zYIucTd_bxUCL4BVbwW2AvbbFsaG7ISmVdGu0XUTmhXs1KrEfUVLRJhE4Dx99hAZXm1_HlYMUeJcMQ_oHOhV94ENFIJaRBhACCpYBCpEBCAMQABqGATCBgwKBgF9t2YJGAJkRRFq6fWhi3m1TFW1UOE0f6ZrfYhHAkpqGlKlh0QVfeTNPpeJhi75xXzCe6oReRUm-0DbqDNhTShC7uGUv1INYnRBQWH6E-5Fc5XrbDFSuGQw2EYjNfHy_HefHJXxQKAqPvxBDKMKkHgV58WtM6rC8jRi9sdX_ig2NIJeRBhABCpYBCpEBCAQQABqGATCBgwKBgGB4d6eLGUBv-Q6EPLehC4S-yuE2HB-_rJ7WkeYwyp-xIPolPrd-PQme2utHB4ZgpXHIu_OFksDe_0bPgZniNRSVRbl7W49DgS5Ya3kMfrYB4DnF5Fta5tn1oV6EwxYD4JONpFTenOJALPGTPawxXEfon_peiHOSBuQMu3_Vn-l1IJiRBhADCpcBCpIBCAUQABqHATCBhAKBgQCTJMKIfmfeZpaI7Q9rnsc29gdWawK7TnpVKRHws1iY7EUlYROeVcMdAwEqVM6f8BVCKLGgzQ7Gar_uuxfUGKwqEQzoppDraw4F75J464-7D5f6_oJQuGIBHZxqbMONtLjBCXRUhQW5szBLmTQ_R3qaJb5vf-h0APZfkYhq1cTttSCZkQYQBAqWAQqRAQgLEAAahgEwgYMCgYBvvGVH_M2H8qxxv94yaDYUTWbRnJ1uiIYc59KIQlfFimMPhSS7x2tqUa2-hI55JiII0Xym6GNkwLhyc1xtWChpVuIdSnbvttbrt4weDMLHqTwNOF6qAsVKGKT1Yh8yf-qb-DSmicgvFc74mBQm_6gAY1iQsf33YX8578ClhKBWHSCVkQYQAAqXAQqSAQgMEAAahwEwgYQCgYEAkuzFcd5TJu7lYWYe2hQLFfUWIIj91BvQQLa_Thln4YtGCO8gG1KJqJm-YlmJOWQG0B7H_5RVhxUxV9KpmFnsDVkzUFKOsCBaYGXc12xPVioawUlAwp5qp3QQtZyx_se97YIoLzuLr46UkLcLnkIrp-Jo46QzYi_QHq45WTm8MQ0glpEGEAIKlwEKkgEIDRAAGocBMIGEAoGBAIUzbxOknXf_rNt17_ir8JlWvrtnCWsQd1MAnl5mgArvavDtKeBYHzi5_Ak7DHlLzuA6YE8W175FxLFKpN2hkz-l-M7ltUSd8N1BvJRhK4t6WffWfC_1wPyoAbeSN2Yb1jygtZJQ8wGoXHcJQUXiMit3eFNyylwsJFj1gzAR4JCdIJeRBhABCpYBCpEBCA4QABqGATCBgwKBgFMcbEpl9ukVR6AO_R6sMyiU11I8b8MBSUCEC15iKsrVO8v_m47_TRRjWPYtQ9eZ7o1ocNJHaGUU7qqInFqtFaVnIceP6NmCsXhjs3MLrWPS8IRAy4Zf4FKmGOx3N9O2vemjUygZ9vUiSkULdVrecinRaT8JQ5RG4bUMY04XGIwFIJiRBhADCpYBCpEBCA8QABqGATCBgwKBgGpCkW-NR3li8GlRvqpq2YZGSIgm_PTyDI2Zwfw69grsBmPpVFW48Vw7xoMN35zcrojEpialB_uQzlpLYOvsMl634CRIuj-n1QE3-gaZTTTE8mg-AR4mcxnTKThPnRQpbuOlYAnriwiasWiQEMbGjq_HmWioYYxFo9USlklQn4-9IJmRBhAEEpUBCpIBCAYQABqHATCBhAKBgQCoZkFGm9oLTqjeXZAq6j5S6i7K20V0lNdBBLqfmFBIRuTkYxhs4vUYnWjZrKRAd5bp6_py0csmFmpl_5Yh0b-2pdo_E5PNP7LGRzKyKSiFddyykKKzVOazH8YYldDAfE8Z5HoS9e48an5JsPg0jr-TPu34DnJq3yv2a6dqiKL9zSCakQYSlQEKkgEIEBAAGocBMIGEAoGBALhrihbf3EpjDQS2sCQHazoFgN0nBbE9eesnnFTfzQELXb2gnJU9enmV_aDqaHKjgtLIPpCgn40lHrn5k6mvH5OdedyI6cCzE-N-GFp3nAq0NDJyMe0fhtIRD__CbT0ulcvkeow65ubXWfw6dBC2gR_34rdMe_L_TGRLMWjDULbNIJ"; + static const TString UNSUPPORTED_VERSION_USER_TICKET = "2:user:CA0Q__________9_GiQKAwjIAwoCCHsQyAMaCGJiOnNlc3MxGghiYjpzZXNzMiASKAE:KJFv5EcXn9krYk19LCvlFrhMW-R4q8mKfXJXCd-RBVBgUQzCOR1Dx2FiOyU-BxUoIsaU0PiwTjbVY5I2onJDilge70Cl5zEPI9pfab2qwklACq_ZBUvD1tzrfNUr88otBGAziHASJWgyVDkhyQ3p7YbN38qpb0vGQrYNxlk4e2I"; + static const TString USER_TICKET_PROTOBUF = "CA0Q__________9_GiQKAwjIAwoCCHsQyAMaCGJiOnNlc3MxGghiYjpzZXNzMiASKAE"; + static const TString VALID_USER_TICKET_1 = "3:user:CA0Q__________9_GiQKAwjIAwoCCHsQyAMaCGJiOnNlc3MxGghiYjpzZXNzMiASKAE:KJFv5EcXn9krYk19LCvlFrhMW-R4q8mKfXJXCd-RBVBgUQzCOR1Dx2FiOyU-BxUoIsaU0PiwTjbVY5I2onJDilge70Cl5zEPI9pfab2qwklACq_ZBUvD1tzrfNUr88otBGAziHASJWgyVDkhyQ3p7YbN38qpb0vGQrYNxlk4e2I"; + static const TString VALID_USER_TICKET_2 = "3:user:CA0Q__________9_GhAKAwjIAwoCCHsQyAMgEigB:KRibGYTJUA2ns0Fn7VYqeMZ1-GdscB1o9pRzELyr7QJrJsfsE8Y_HoVvB8Npr-oalv6AXOpagSc8HpZjAQz8zKMAVE_tI0tL-9DEsHirpawEbpy7OWV7-k18o1m-RaDaKeTlIB45KHbBul1-9aeKkortBfbbXtz_Qy9r_mfFPiQ"; + static const TString VALID_USER_TICKET_3 = "3:user:CA0Q__________9_Go8bCgIIAAoCCAEKAggCCgIIAwoCCAQKAggFCgIIBgoCCAcKAggICgIICQoCCAoKAggLCgIIDAoCCA0KAggOCgIIDwoCCBAKAggRCgIIEgoCCBMKAggUCgIIFQoCCBYKAggXCgIIGAoCCBkKAggaCgIIGwoCCBwKAggdCgIIHgoCCB8KAgggCgIIIQoCCCIKAggjCgIIJAoCCCUKAggmCgIIJwoCCCgKAggpCgIIKgoCCCsKAggsCgIILQoCCC4KAggvCgIIMAoCCDEKAggyCgIIMwoCCDQKAgg1CgIINgoCCDcKAgg4CgIIOQoCCDoKAgg7CgIIPAoCCD0KAgg-CgIIPwoCCEAKAghBCgIIQgoCCEMKAghECgIIRQoCCEYKAghHCgIISAoCCEkKAghKCgIISwoCCEwKAghNCgIITgoCCE8KAghQCgIIUQoCCFIKAghTCgIIVAoCCFUKAghWCgIIVwoCCFgKAghZCgIIWgoCCFsKAghcCgIIXQoCCF4KAghfCgIIYAoCCGEKAghiCgIIYwoCCGQKAghlCgIIZgoCCGcKAghoCgIIaQoCCGoKAghrCgIIbAoCCG0KAghuCgIIbwoCCHAKAghxCgIIcgoCCHMKAgh0CgIIdQoCCHYKAgh3CgIIeAoCCHkKAgh6CgIIewoCCHwKAgh9CgIIfgoCCH8KAwiAAQoDCIEBCgMIggEKAwiDAQoDCIQBCgMIhQEKAwiGAQoDCIcBCgMIiAEKAwiJAQoDCIoBCgMIiwEKAwiMAQoDCI0BCgMIjgEKAwiPAQoDCJABCgMIkQEKAwiSAQoDCJMBCgMIlAEKAwiVAQoDCJYBCgMIlwEKAwiYAQoDCJkBCgMImgEKAwibAQoDCJwBCgMInQEKAwieAQoDCJ8BCgMIoAEKAwihAQoDCKIBCgMIowEKAwikAQoDCKUBCgMIpgEKAwinAQoDCKgBCgMIqQEKAwiqAQoDCKsBCgMIrAEKAwitAQoDCK4BCgMIrwEKAwiwAQoDCLEBCgMIsgEKAwizAQoDCLQBCgMItQEKAwi2AQoDCLcBCgMIuAEKAwi5AQoDCLoBCgMIuwEKAwi8AQoDCL0BCgMIvgEKAwi_AQoDCMABCgMIwQEKAwjCAQoDCMMBCgMIxAEKAwjFAQoDCMYBCgMIxwEKAwjIAQoDCMkBCgMIygEKAwjLAQoDCMwBCgMIzQEKAwjOAQoDCM8BCgMI0AEKAwjRAQoDCNIBCgMI0wEKAwjUAQoDCNUBCgMI1gEKAwjXAQoDCNgBCgMI2QEKAwjaAQoDCNsBCgMI3AEKAwjdAQoDCN4BCgMI3wEKAwjgAQoDCOEBCgMI4gEKAwjjAQoDCOQBCgMI5QEKAwjmAQoDCOcBCgMI6AEKAwjpAQoDCOoBCgMI6wEKAwjsAQoDCO0BCgMI7gEKAwjvAQoDCPABCgMI8QEKAwjyAQoDCPMBCgMI9AEKAwj1AQoDCPYBCgMI9wEKAwj4AQoDCPkBCgMI-gEKAwj7AQoDCPwBCgMI_QEKAwj-AQoDCP8BCgMIgAIKAwiBAgoDCIICCgMIgwIKAwiEAgoDCIUCCgMIhgIKAwiHAgoDCIgCCgMIiQIKAwiKAgoDCIsCCgMIjAIKAwiNAgoDCI4CCgMIjwIKAwiQAgoDCJECCgMIkgIKAwiTAgoDCJQCCgMIlQIKAwiWAgoDCJcCCgMImAIKAwiZAgoDCJoCCgMImwIKAwicAgoDCJ0CCgMIngIKAwifAgoDCKACCgMIoQIKAwiiAgoDCKMCCgMIpAIKAwilAgoDCKYCCgMIpwIKAwioAgoDCKkCCgMIqgIKAwirAgoDCKwCCgMIrQIKAwiuAgoDCK8CCgMIsAIKAwixAgoDCLICCgMIswIKAwi0AgoDCLUCCgMItgIKAwi3AgoDCLgCCgMIuQIKAwi6AgoDCLsCCgMIvAIKAwi9AgoDCL4CCgMIvwIKAwjAAgoDCMECCgMIwgIKAwjDAgoDCMQCCgMIxQIKAwjGAgoDCMcCCgMIyAIKAwjJAgoDCMoCCgMIywIKAwjMAgoDCM0CCgMIzgIKAwjPAgoDCNACCgMI0QIKAwjSAgoDCNMCCgMI1AIKAwjVAgoDCNYCCgMI1wIKAwjYAgoDCNkCCgMI2gIKAwjbAgoDCNwCCgMI3QIKAwjeAgoDCN8CCgMI4AIKAwjhAgoDCOICCgMI4wIKAwjkAgoDCOUCCgMI5gIKAwjnAgoDCOgCCgMI6QIKAwjqAgoDCOsCCgMI7AIKAwjtAgoDCO4CCgMI7wIKAwjwAgoDCPECCgMI8gIKAwjzAgoDCPQCCgMI9QIKAwj2AgoDCPcCCgMI-AIKAwj5AgoDCPoCCgMI-wIKAwj8AgoDCP0CCgMI_gIKAwj_AgoDCIADCgMIgQMKAwiCAwoDCIMDCgMIhAMKAwiFAwoDCIYDCgMIhwMKAwiIAwoDCIkDCgMIigMKAwiLAwoDCIwDCgMIjQMKAwiOAwoDCI8DCgMIkAMKAwiRAwoDCJIDCgMIkwMKAwiUAwoDCJUDCgMIlgMKAwiXAwoDCJgDCgMImQMKAwiaAwoDCJsDCgMInAMKAwidAwoDCJ4DCgMInwMKAwigAwoDCKEDCgMIogMKAwijAwoDCKQDCgMIpQMKAwimAwoDCKcDCgMIqAMKAwipAwoDCKoDCgMIqwMKAwisAwoDCK0DCgMIrgMKAwivAwoDCLADCgMIsQMKAwiyAwoDCLMDCgMItAMKAwi1AwoDCLYDCgMItwMKAwi4AwoDCLkDCgMIugMKAwi7AwoDCLwDCgMIvQMKAwi-AwoDCL8DCgMIwAMKAwjBAwoDCMIDCgMIwwMKAwjEAwoDCMUDCgMIxgMKAwjHAwoDCMgDCgMIyQMKAwjKAwoDCMsDCgMIzAMKAwjNAwoDCM4DCgMIzwMKAwjQAwoDCNEDCgMI0gMKAwjTAwoDCNQDCgMI1QMKAwjWAwoDCNcDCgMI2AMKAwjZAwoDCNoDCgMI2wMKAwjcAwoDCN0DCgMI3gMKAwjfAwoDCOADCgMI4QMKAwjiAwoDCOMDCgMI5AMKAwjlAwoDCOYDCgMI5wMKAwjoAwoDCOkDCgMI6gMKAwjrAwoDCOwDCgMI7QMKAwjuAwoDCO8DCgMI8AMKAwjxAwoDCPIDCgMI8wMQyAMaCGJiOnNlc3MxGgliYjpzZXNzMTAaCmJiOnNlc3MxMDAaCWJiOnNlc3MxMRoJYmI6c2VzczEyGgliYjpzZXNzMTMaCWJiOnNlc3MxNBoJYmI6c2VzczE1GgliYjpzZXNzMTYaCWJiOnNlc3MxNxoJYmI6c2VzczE4GgliYjpzZXNzMTkaCGJiOnNlc3MyGgliYjpzZXNzMjAaCWJiOnNlc3MyMRoJYmI6c2VzczIyGgliYjpzZXNzMjMaCWJiOnNlc3MyNBoJYmI6c2VzczI1GgliYjpzZXNzMjYaCWJiOnNlc3MyNxoJYmI6c2VzczI4GgliYjpzZXNzMjkaCGJiOnNlc3MzGgliYjpzZXNzMzAaCWJiOnNlc3MzMRoJYmI6c2VzczMyGgliYjpzZXNzMzMaCWJiOnNlc3MzNBoJYmI6c2VzczM1GgliYjpzZXNzMzYaCWJiOnNlc3MzNxoJYmI6c2VzczM4GgliYjpzZXNzMzkaCGJiOnNlc3M0GgliYjpzZXNzNDAaCWJiOnNlc3M0MRoJYmI6c2VzczQyGgliYjpzZXNzNDMaCWJiOnNlc3M0NBoJYmI6c2VzczQ1GgliYjpzZXNzNDYaCWJiOnNlc3M0NxoJYmI6c2VzczQ4GgliYjpzZXNzNDkaCGJiOnNlc3M1GgliYjpzZXNzNTAaCWJiOnNlc3M1MRoJYmI6c2VzczUyGgliYjpzZXNzNTMaCWJiOnNlc3M1NBoJYmI6c2VzczU1GgliYjpzZXNzNTYaCWJiOnNlc3M1NxoJYmI6c2VzczU4GgliYjpzZXNzNTkaCGJiOnNlc3M2GgliYjpzZXNzNjAaCWJiOnNlc3M2MRoJYmI6c2VzczYyGgliYjpzZXNzNjMaCWJiOnNlc3M2NBoJYmI6c2VzczY1GgliYjpzZXNzNjYaCWJiOnNlc3M2NxoJYmI6c2VzczY4GgliYjpzZXNzNjkaCGJiOnNlc3M3GgliYjpzZXNzNzAaCWJiOnNlc3M3MRoJYmI6c2VzczcyGgliYjpzZXNzNzMaCWJiOnNlc3M3NBoJYmI6c2Vzczc1GgliYjpzZXNzNzYaCWJiOnNlc3M3NxoJYmI6c2Vzczc4GgliYjpzZXNzNzkaCGJiOnNlc3M4GgliYjpzZXNzODAaCWJiOnNlc3M4MRoJYmI6c2VzczgyGgliYjpzZXNzODMaCWJiOnNlc3M4NBoJYmI6c2Vzczg1GgliYjpzZXNzODYaCWJiOnNlc3M4NxoJYmI6c2Vzczg4GgliYjpzZXNzODkaCGJiOnNlc3M5GgliYjpzZXNzOTAaCWJiOnNlc3M5MRoJYmI6c2VzczkyGgliYjpzZXNzOTMaCWJiOnNlc3M5NBoJYmI6c2Vzczk1GgliYjpzZXNzOTYaCWJiOnNlc3M5NxoJYmI6c2Vzczk4GgliYjpzZXNzOTkgEigB:CX8PIOrxJnQqFXl7wAsiHJ_1VGjoI-asNlCXb8SE8jtI2vdh9x6CqbAurSgIlAAEgotVP-nuUR38x_a9YJuXzmG5AvJ458apWQtODHIDIX6ZaIwMxjS02R7S5LNqXa0gAuU_R6bCWpZdWe2uLMkdpu5KHbDgW08g-uaP_nceDOk"; + + Y_UNIT_TEST(Case1Test) { + TUserContext context1(EBlackboxEnv::Test, NUnittest::TVMKNIFE_PUBLIC_KEYS); + + TCheckedUserTicket checkedTicket1 = context1.Check("2:serv:CgYIDRCUkQYQDBgcIgdiYjpzZXNzIghiYjpzZXNzMg:ERmeH_yzC7K_QsoHTyw7llCsyExEz3CoEopPIuivA0ZAtTaFq_Pa0l9Fhhx_NX9WpOp2CPyY5cFc4PXhcO83jCB7-EGvHNxGN-j2NQalERzPiKqkDCO0Q5etLzSzrfTlvMz7sXDvELNBHyA0PkAQnbz4supY0l-0Q6JBYSEF3zOVMjjE-HeQIFL3ats3_PakaUMWRvgQQ88pVdYZqAtbDw9PlTla7ommygVZQjcfNFXV1pJKRgOCLs-YyCjOJHLKL04zYj0X6KsOCTUeqhj7ml96wLZ-g1X9tyOR2WAr2Ctq7wIEHwqhxOLgOSKqm05xH6Vi3E_hekf50oe2jPfKEA"); + UNIT_ASSERT_EQUAL(ETicketStatus::UnsupportedVersion, checkedTicket1.GetStatus()); + UNIT_ASSERT(!checkedTicket1); + + TUserContext context2 = std::move(context1); + TUserContext context3(std::move(context2)); + TCheckedUserTicket checkedTicket2 = context3.Check(VALID_USER_TICKET_1); + UNIT_ASSERT_EQUAL(ETicketStatus::Ok, checkedTicket2.GetStatus()); + TCheckedUserTicket checkedTicket3 = std::move(checkedTicket2); + TCheckedUserTicket checkedTicket4(std::move(checkedTicket3)); + UNIT_ASSERT_EQUAL(ETicketStatus::Ok, checkedTicket4.GetStatus()); + } + + Y_UNIT_TEST(ContextTest) { + TUserContext context(EBlackboxEnv::Prod, NUnittest::TVMKNIFE_PUBLIC_KEYS); + } + + Y_UNIT_TEST(ContextExceptionsTest) { + UNIT_ASSERT_EXCEPTION(TUserContext(EBlackboxEnv::Prod, EMPTY_TVM_KEYS), TEmptyTvmKeysException); + UNIT_ASSERT_EXCEPTION(TUserContext(EBlackboxEnv::Prod, MALFORMED_TVM_KEYS), TMalformedTvmKeysException); + } + + Y_UNIT_TEST(Ticket1Test) { + TUserContext context(EBlackboxEnv::Test, NUnittest::TVMKNIFE_PUBLIC_KEYS); + auto checkedTicket = context.Check(VALID_USER_TICKET_1); + UNIT_ASSERT_EQUAL(ETicketStatus::Ok, checkedTicket.GetStatus()); + UNIT_ASSERT_EQUAL(TUids({456, 123}), checkedTicket.GetUids()); + UNIT_ASSERT_EQUAL(456, checkedTicket.GetDefaultUid()); + UNIT_ASSERT_EQUAL(TScopes({"bb:sess1", "bb:sess2"}), checkedTicket.GetScopes()); + UNIT_ASSERT(checkedTicket.HasScope("bb:sess1")); + UNIT_ASSERT(checkedTicket.HasScope("bb:sess2")); + UNIT_ASSERT(!checkedTicket.HasScope("bb:sess3")); + UNIT_ASSERT_EQUAL("ticket_type=user;expiration_time=9223372036854775807;scope=bb:sess1;scope=bb:sess2;default_uid=456;uid=456;uid=123;env=Test;", checkedTicket.DebugInfo()); + } + + Y_UNIT_TEST(Ticket2Test) { + TUserContext context(EBlackboxEnv::Test, NUnittest::TVMKNIFE_PUBLIC_KEYS); + auto checkedTicket = context.Check(VALID_USER_TICKET_2); + UNIT_ASSERT_EQUAL(ETicketStatus::Ok, checkedTicket.GetStatus()); + UNIT_ASSERT_VALUES_EQUAL("ticket_type=user;expiration_time=9223372036854775807;default_uid=456;uid=456;uid=123;env=Test;", checkedTicket.DebugInfo()); + } + + Y_UNIT_TEST(Ticket3Test) { + TUserContext context(EBlackboxEnv::Test, NUnittest::TVMKNIFE_PUBLIC_KEYS); + auto checkedTicket = context.Check(VALID_USER_TICKET_3); + UNIT_ASSERT_EQUAL(ETicketStatus::Ok, checkedTicket.GetStatus()); + UNIT_ASSERT_VALUES_EQUAL("ticket_type=user;expiration_time=9223372036854775807;scope=bb:sess1;scope=bb:sess10;scope=bb:sess100;scope=bb:sess11;scope=bb:sess12;scope=bb:sess13;scope=bb:sess14;scope=bb:sess15;scope=bb:sess16;scope=bb:sess17;scope=bb:sess18;scope=bb:sess19;scope=bb:sess2;scope=bb:sess20;scope=bb:sess21;scope=bb:sess22;scope=bb:sess23;scope=bb:sess24;scope=bb:sess25;scope=bb:sess26;scope=bb:sess27;scope=bb:sess28;scope=bb:sess29;scope=bb:sess3;scope=bb:sess30;scope=bb:sess31;scope=bb:sess32;scope=bb:sess33;scope=bb:sess34;scope=bb:sess35;scope=bb:sess36;scope=bb:sess37;scope=bb:sess38;scope=bb:sess39;scope=bb:sess4;scope=bb:sess40;scope=bb:sess41;scope=bb:sess42;scope=bb:sess43;scope=bb:sess44;scope=bb:sess45;scope=bb:sess46;scope=bb:sess47;scope=bb:sess48;scope=bb:sess49;scope=bb:sess5;scope=bb:sess50;scope=bb:sess51;scope=bb:sess52;scope=bb:sess53;scope=bb:sess54;scope=bb:sess55;scope=bb:sess56;scope=bb:sess57;scope=bb:sess58;scope=bb:sess59;scope=bb:sess6;scope=bb:sess60;scope=bb:sess61;scope=bb:sess62;scope=bb:sess63;scope=bb:sess64;scope=bb:sess65;scope=bb:sess66;scope=bb:sess67;scope=bb:sess68;scope=bb:sess69;scope=bb:sess7;scope=bb:sess70;scope=bb:sess71;scope=bb:sess72;scope=bb:sess73;scope=bb:sess74;scope=bb:sess75;scope=bb:sess76;scope=bb:sess77;scope=bb:sess78;scope=bb:sess79;scope=bb:sess8;scope=bb:sess80;scope=bb:sess81;scope=bb:sess82;scope=bb:sess83;scope=bb:sess84;scope=bb:sess85;scope=bb:sess86;scope=bb:sess87;scope=bb:sess88;scope=bb:sess89;scope=bb:sess9;scope=bb:sess90;scope=bb:sess91;scope=bb:sess92;scope=bb:sess93;scope=bb:sess94;scope=bb:sess95;scope=bb:sess96;scope=bb:sess97;scope=bb:sess98;scope=bb:sess99;default_uid=456;uid=0;uid=1;uid=2;uid=3;uid=4;uid=5;uid=6;uid=7;uid=8;uid=9;uid=10;uid=11;uid=12;uid=13;uid=14;uid=15;uid=16;uid=17;uid=18;uid=19;uid=20;uid=21;uid=22;uid=23;uid=24;uid=25;uid=26;uid=27;uid=28;uid=29;uid=30;uid=31;uid=32;uid=33;uid=34;uid=35;uid=36;uid=37;uid=38;uid=39;uid=40;uid=41;uid=42;uid=43;uid=44;uid=45;uid=46;uid=47;uid=48;uid=49;uid=50;uid=51;uid=52;uid=53;uid=54;uid=55;uid=56;uid=57;uid=58;uid=59;uid=60;uid=61;uid=62;uid=63;uid=64;uid=65;uid=66;uid=67;uid=68;uid=69;uid=70;uid=71;uid=72;uid=73;uid=74;uid=75;uid=76;uid=77;uid=78;uid=79;uid=80;uid=81;uid=82;uid=83;uid=84;uid=85;uid=86;uid=87;uid=88;uid=89;uid=90;uid=91;uid=92;uid=93;uid=94;uid=95;uid=96;uid=97;uid=98;uid=99;uid=100;uid=101;uid=102;uid=103;uid=104;uid=105;uid=106;uid=107;uid=108;uid=109;uid=110;uid=111;uid=112;uid=113;uid=114;uid=115;uid=116;uid=117;uid=118;uid=119;uid=120;uid=121;uid=122;uid=123;uid=124;uid=125;uid=126;uid=127;uid=128;uid=129;uid=130;uid=131;uid=132;uid=133;uid=134;uid=135;uid=136;uid=137;uid=138;uid=139;uid=140;uid=141;uid=142;uid=143;uid=144;uid=145;uid=146;uid=147;uid=148;uid=149;uid=150;uid=151;uid=152;uid=153;uid=154;uid=155;uid=156;uid=157;uid=158;uid=159;uid=160;uid=161;uid=162;uid=163;uid=164;uid=165;uid=166;uid=167;uid=168;uid=169;uid=170;uid=171;uid=172;uid=173;uid=174;uid=175;uid=176;uid=177;uid=178;uid=179;uid=180;uid=181;uid=182;uid=183;uid=184;uid=185;uid=186;uid=187;uid=188;uid=189;uid=190;uid=191;uid=192;uid=193;uid=194;uid=195;uid=196;uid=197;uid=198;uid=199;uid=200;uid=201;uid=202;uid=203;uid=204;uid=205;uid=206;uid=207;uid=208;uid=209;uid=210;uid=211;uid=212;uid=213;uid=214;uid=215;uid=216;uid=217;uid=218;uid=219;uid=220;uid=221;uid=222;uid=223;uid=224;uid=225;uid=226;uid=227;uid=228;uid=229;uid=230;uid=231;uid=232;uid=233;uid=234;uid=235;uid=236;uid=237;uid=238;uid=239;uid=240;uid=241;uid=242;uid=243;uid=244;uid=245;uid=246;uid=247;uid=248;uid=249;uid=250;uid=251;uid=252;uid=253;uid=254;uid=255;uid=256;uid=257;uid=258;uid=259;uid=260;uid=261;uid=262;uid=263;uid=264;uid=265;uid=266;uid=267;uid=268;uid=269;uid=270;uid=271;uid=272;uid=273;uid=274;uid=275;uid=276;uid=277;uid=278;uid=279;uid=280;uid=281;uid=282;uid=283;uid=284;uid=285;uid=286;uid=287;uid=288;uid=289;uid=290;uid=291;uid=292;uid=293;uid=294;uid=295;uid=296;uid=297;uid=298;uid=299;uid=300;uid=301;uid=302;uid=303;uid=304;uid=305;uid=306;uid=307;uid=308;uid=309;uid=310;uid=311;uid=312;uid=313;uid=314;uid=315;uid=316;uid=317;uid=318;uid=319;uid=320;uid=321;uid=322;uid=323;uid=324;uid=325;uid=326;uid=327;uid=328;uid=329;uid=330;uid=331;uid=332;uid=333;uid=334;uid=335;uid=336;uid=337;uid=338;uid=339;uid=340;uid=341;uid=342;uid=343;uid=344;uid=345;uid=346;uid=347;uid=348;uid=349;uid=350;uid=351;uid=352;uid=353;uid=354;uid=355;uid=356;uid=357;uid=358;uid=359;uid=360;uid=361;uid=362;uid=363;uid=364;uid=365;uid=366;uid=367;uid=368;uid=369;uid=370;uid=371;uid=372;uid=373;uid=374;uid=375;uid=376;uid=377;uid=378;uid=379;uid=380;uid=381;uid=382;uid=383;uid=384;uid=385;uid=386;uid=387;uid=388;uid=389;uid=390;uid=391;uid=392;uid=393;uid=394;uid=395;uid=396;uid=397;uid=398;uid=399;uid=400;uid=401;uid=402;uid=403;uid=404;uid=405;uid=406;uid=407;uid=408;uid=409;uid=410;uid=411;uid=412;uid=413;uid=414;uid=415;uid=416;uid=417;uid=418;uid=419;uid=420;uid=421;uid=422;uid=423;uid=424;uid=425;uid=426;uid=427;uid=428;uid=429;uid=430;uid=431;uid=432;uid=433;uid=434;uid=435;uid=436;uid=437;uid=438;uid=439;uid=440;uid=441;uid=442;uid=443;uid=444;uid=445;uid=446;uid=447;uid=448;uid=449;uid=450;uid=451;uid=452;uid=453;uid=454;uid=455;uid=456;uid=457;uid=458;uid=459;uid=460;uid=461;uid=462;uid=463;uid=464;uid=465;uid=466;uid=467;uid=468;uid=469;uid=470;uid=471;uid=472;uid=473;uid=474;uid=475;uid=476;uid=477;uid=478;uid=479;uid=480;uid=481;uid=482;uid=483;uid=484;uid=485;uid=486;uid=487;uid=488;uid=489;uid=490;uid=491;uid=492;uid=493;uid=494;uid=495;uid=496;uid=497;uid=498;uid=499;env=Test;", checkedTicket.DebugInfo()); + } + + Y_UNIT_TEST(TicketErrorsTest) { + TUserContext contextTest(EBlackboxEnv::Test, NUnittest::TVMKNIFE_PUBLIC_KEYS); + auto checkedTicket1 = contextTest.Check(UNSUPPORTED_VERSION_USER_TICKET); + UNIT_ASSERT_EQUAL(ETicketStatus::UnsupportedVersion, checkedTicket1.GetStatus()); + + auto checkedTicket2 = contextTest.Check(EXPIRED_USER_TICKET); + UNIT_ASSERT_EQUAL(ETicketStatus::Expired, checkedTicket2.GetStatus()); + + TUserContext contextProd(EBlackboxEnv::Prod, NUnittest::TVMKNIFE_PUBLIC_KEYS); + auto checkedTicket3 = contextProd.Check(VALID_USER_TICKET_1); + UNIT_ASSERT_EQUAL(ETicketStatus::InvalidBlackboxEnv, checkedTicket3.GetStatus()); + } + + Y_UNIT_TEST(TicketExceptionsTest) { + TUserContext contextTest(EBlackboxEnv::Test, NUnittest::TVMKNIFE_PUBLIC_KEYS); + auto checkedTicket = contextTest.Check(EXPIRED_USER_TICKET); + UNIT_ASSERT_EQUAL(ETicketStatus::Expired, checkedTicket.GetStatus()); + + UNIT_ASSERT_EXCEPTION(checkedTicket.GetDefaultUid(), TNotAllowedException); + UNIT_ASSERT_EXCEPTION(checkedTicket.GetUids(), TNotAllowedException); + UNIT_ASSERT_EXCEPTION(checkedTicket.GetScopes(), TNotAllowedException); + UNIT_ASSERT_EXCEPTION(checkedTicket.HasScope(""), TNotAllowedException); + UNIT_ASSERT_NO_EXCEPTION(bool(checkedTicket)); + UNIT_ASSERT_NO_EXCEPTION(checkedTicket.DebugInfo()); + UNIT_ASSERT_NO_EXCEPTION(checkedTicket.GetStatus()); + } + + Y_UNIT_TEST(ResetKeysTest) { + TUserContext context(EBlackboxEnv::Test, NUnittest::TVMKNIFE_PUBLIC_KEYS); + auto checkedTicket = context.Check(VALID_USER_TICKET_1); + UNIT_ASSERT_EQUAL(ETicketStatus::Ok, checkedTicket.GetStatus()); + } +} diff --git a/library/cpp/tvmauth/src/ut/service_ut.cpp b/library/cpp/tvmauth/src/ut/service_ut.cpp new file mode 100644 index 0000000000..5b6b5143bd --- /dev/null +++ b/library/cpp/tvmauth/src/ut/service_ut.cpp @@ -0,0 +1,156 @@ +#include <library/cpp/tvmauth/src/service_impl.h> +#include <library/cpp/tvmauth/src/utils.h> + +#include <library/cpp/tvmauth/exception.h> +#include <library/cpp/tvmauth/unittest.h> + +#include <library/cpp/testing/unittest/registar.h> + +#include <util/string/cast.h> + +using namespace NTvmAuth; + +Y_UNIT_TEST_SUITE(ServiceTestSuite) { + Y_UNIT_TEST_DECLARE(TicketProtoTest); +} + +class TTestServiceTicketImpl: public TCheckedServiceTicket::TImpl { + using TCheckedServiceTicket::TImpl::TImpl; + Y_UNIT_TEST_FRIEND(ServiceTestSuite, TicketProtoTest); +}; + +Y_UNIT_TEST_SUITE_IMPLEMENTATION(ServiceTestSuite) { + static const TString EMPTY_TVM_KEYS = "1:CpgCCpMCCAEQABqIAjCCAQQCggEAcLEXeH67FQESFUn4_7wnX7wN0PUrBoUsm3QQ4W5vC-qz6sXaEjSwnTV8w1o-z6X9KPLlhzMQvuS38NCNfK4uvJ4Zvfp3YsXJ25-rYtbnrYJHNvHohD-kPCCw_yZpMp21JdWigzQGuV7CtrxUhF-NNrsnUaJrE5-OpEWNt4X6nCItKIYeVcSK6XJUbEWbrNCRbvkSc4ak2ymFeMuHYJVjxh4eQbk7_ZPzodP0WvF6eUYrYeb42imVEOR8ofVLQWE5DVnb1z_TqZm4i1XkS7jMwZuBxBRw8DGdYei0lT_sAf7KST2jC0590NySB3vsBgWEVs1OdUUWA6r-Dvx9dsOQtSCVkQYQAAqZAgqUAggCEAAaiQIwggEFAoIBAQDhEBM5-6YsPWfogKtbluJoCX1WV2KdzOaQ0-OlRbBzeCzw-eQKu12c8WakHBbeCMd1I1TU64SDkDorWjXGIa_2xT6N3zzNAE50roTbPCcmeQrps26woTYfYIuqDdoxYKZNr0lvNLLW47vBr7EKqo1S4KSj7aXK_XYeEvUgIgf3nVIcNrio7VTnFmGGVQCepaL1Hi1gN4yIXjVZ06PBPZ-DxSRu6xOGbFrfKMJeMPs7KOyE-26Q3xOXdTIa1X-zYIucTd_bxUCL4BVbwW2AvbbFsaG7ISmVdGu0XUTmhXs1KrEfUVLRJhE4Dx99hAZXm1_HlYMUeJcMQ_oHOhV94ENFIJaRBhACCpYBCpEBCAMQABqGATCBgwKBgF9t2YJGAJkRRFq6fWhi3m1TFW1UOE0f6ZrfYhHAkpqGlKlh0QVfeTNPpeJhi75xXzCe6oReRUm-0DbqDNhTShC7uGUv1INYnRBQWH6E-5Fc5XrbDFSuGQw2EYjNfHy_HefHJXxQKAqPvxBDKMKkHgV58WtM6rC8jRi9sdX_ig2NIJeRBhABCpYBCpEBCAQQABqGATCBgwKBgGB4d6eLGUBv-Q6EPLehC4S-yuE2HB-_rJ7WkeYwyp-xIPolPrd-PQme2utHB4ZgpXHIu_OFksDe_0bPgZniNRSVRbl7W49DgS5Ya3kMfrYB4DnF5Fta5tn1oV6EwxYD4JONpFTenOJALPGTPawxXEfon_peiHOSBuQMu3_Vn-l1IJiRBhADCpcBCpIBCAUQABqHATCBhAKBgQCTJMKIfmfeZpaI7Q9rnsc29gdWawK7TnpVKRHws1iY7EUlYROeVcMdAwEqVM6f8BVCKLGgzQ7Gar_uuxfUGKwqEQzoppDraw4F75J464-7D5f6_oJQuGIBHZxqbMONtLjBCXRUhQW5szBLmTQ_R3qaJb5vf-h0APZfkYhq1cTttSCZkQYQBAqWAQqRAQgLEAAahgEwgYMCgYBvvGVH_M2H8qxxv94yaDYUTWbRnJ1uiIYc59KIQlfFimMPhSS7x2tqUa2-hI55JiII0Xym6GNkwLhyc1xtWChpVuIdSnbvttbrt4weDMLHqTwNOF6qAsVKGKT1Yh8yf-qb-DSmicgvFc74mBQm_6gAY1iQsf33YX8578ClhKBWHSCVkQYQAAqXAQqSAQgMEAAahwEwgYQCgYEAkuzFcd5TJu7lYWYe2hQLFfUWIIj91BvQQLa_Thln4YtGCO8gG1KJqJm-YlmJOWQG0B7H_5RVhxUxV9KpmFnsDVkzUFKOsCBaYGXc12xPVioawUlAwp5qp3QQtZyx_se97YIoLzuLr46UkLcLnkIrp-Jo46QzYi_QHq45WTm8MQ0glpEGEAIKlwEKkgEIDRAAGocBMIGEAoGBAIUzbxOknXf_rNt17_ir8JlWvrtnCWsQd1MAnl5mgArvavDtKeBYHzi5_Ak7DHlLzuA6YE8W175FxLFKpN2hkz-l-M7ltUSd8N1BvJRhK4t6WffWfC_1wPyoAbeSN2Yb1jygtZJQ8wGoXHcJQUXiMit3eFNyylwsJFj1gzAR4JCdIJeRBhABCpYBCpEBCA4QABqGATCBgwKBgFMcbEpl9ukVR6AO_R6sMyiU11I8b8MBSUCEC15iKsrVO8v_m47_TRRjWPYtQ9eZ7o1ocNJHaGUU7qqInFqtFaVnIceP6NmCsXhjs3MLrWPS8IRAy4Zf4FKmGOx3N9O2vemjUygZ9vUiSkULdVrecinRaT8JQ5RG4bUMY04XGIwFIJiRBhADCpYBCpEBCA8QABqGATCBgwKBgGpCkW-NR3li8GlRvqpq2YZGSIgm_PTyDI2Zwfw69grsBmPpVFW48Vw7xoMN35zcrojEpialB_uQzlpLYOvsMl634CRIuj-n1QE3-gaZTTTE8mg-AR4mcxnTKThPnRQpbuOlYAnriwiasWiQEMbGjq_HmWioYYxFo9USlklQn4-9IJmRBhAE"; + static const TString EXPIRED_SERVICE_TICKET = "3:serv:CBAQACIZCOUBEBwaCGJiOnNlc3MxGghiYjpzZXNzMg:IwfMNJYEqStY_SixwqJnyHOMCPR7-3HHk4uylB2oVRkthtezq-OOA7QizDvx7VABLs_iTlXuD1r5IjufNei_EiV145eaa3HIg4xCdJXCojMexf2UYJz8mF2b0YzFAy6_KWagU7xo13CyKAqzJuQf5MJcSUf0ecY9hVh36cJ51aw"; + static const TString MALFORMED_TVM_KEYS = "1:CpgCCpMCCAEQABqIAjCCAQQCggEAcLEXeH67FQESFUn4_7wnX7wN0PUrBoUsm3QQ4W5vC-qz6sXaEjSwnTV8w1o-z6X9KPLlhzMQvuS38NCNfK4uvJ4Zvfp3YsXJ25-rYtbnrYJHNvHohD-kPCCw_yZpMp21JdWigzQGuV7CtrxUhF-NNrsnUaJrE5-OpEWNt4X6nCItKIYeVcSK6XJUbEWbrNCRbvkSc4ak2ymFeMuHYJVjxh4eQbk7_ZPzodP0WvF6eUYrYeb42imVEOR8ofVLQWE5DVnb1z_TqZm4i1XkS7jMwZuBxBRw8DGdYei0lT_sAf7KST2jC0590NySB3vsBgWEVs1OdUUWA6r-Dvx9dsOQtSCVkQYQAAqZAgqUAggCEAAaiQIwggEFAoIBAQDhEBM5-6YsPWfogKtbluJoCX1WV2KdzOaQ0-OlRbBzeCzw-eQKu12c8WakHBbeCMd1I1TU64SDkDorWjXGIa_2xT6N3zzNAE50roTbPCcmeQrps26woTYfYIuqDdoxYKZNr0lvNLLW47vBr7EKqo1S4KSj7aXK_XYeEvUgIgf3nVIcNrio7VTnFmGGVQCepaL1Hi1gN4yIXjVZ06PBPZ-DxSRu6xOGbFrfKMJeMPs7KOyE-26Q3xOXdTIa1X-zYIucTd_bxUCL4BVbwW2AvbbFsaG7ISmVdGu0XUTmhXs1KrEfUVLRJhE4Dx99hAZXm1_HlYMUeJcMQ_oHOhV94ENFIJaRBhACCpYBCpEBCAMQABqGATCBgwKBgF9t2YJGAJkRRFq6fWhi3m1TFW1UOE0f6ZrfYhHAkpqGlKlh0QVfeTNPpeJhi75xXzCe6oReRUm-0DbqDNhTShC7uGUv1INYnRBQWH6E-5Fc5XrbDFSuGQw2EYjNfHy_HefHJXxQKAqPvxBDKMKkHgV58WtM6rC8jRi9sdX_ig2NIJeRBhABCpYBCpEBCAQQABqGATCBgwKBgGB4d6eLGUBv-Q6EPLehC4S-yuE2HB-_rJ7WkeYwyp-xIPolPrd-PQme2utHB4ZgpXHIu_OFksDe_0bPgZniNRSVRbl7W49DgS5Ya3kMfrYB4DnF5Fta5tn1oV6EwxYD4JONpFTenOJALPGTPawxXEfon_peiHOSBuQMu3_Vn-l1IJiRBhADCpcBCpIBCAUQABqHATCBhAKBgQCTJMKIfmfeZpaI7Q9rnsc29gdWawK7TnpVKRHws1iY7EUlYROeVcMdAwEqVM6f8BVCKLGgzQ7Gar_uuxfUGKwqEQzoppDraw4F75J464-7D5f6_oJQuGIBHZxqbMONtLjBCXRUhQW5szBLmTQ_R3qaJb5vf-h0APZfkYhq1cTttSCZkQYQBAqWAQqRAQgLEAAahgEwgYMCgYBvvGVH_M2H8qxxv94yaDYUTWbRnJ1uiIYc59KIQlfFimMPhSS7x2tqUa2-hI55JiII0Xym6GNkwLhyc1xtWChpVuIdSnbvttbrt4weDMLHqTwNOF6qAsVKGKT1Yh8yf-qb-DSmicgvFc74mBQm_6gAY1iQsf33YX8578ClhKBWHSCVkQYQAAqXAQqSAQgMEAAahwEwgYQCgYEAkuzFcd5TJu7lYWYe2hQLFfUWIIj91BvQQLa_Thln4YtGCO8gG1KJqJm-YlmJOWQG0B7H_5RVhxUxV9KpmFnsDVkzUFKOsCBaYGXc12xPVioawUlAwp5qp3QQtZyx_se97YIoLzuLr46UkLcLnkIrp-Jo46QzYi_QHq45WTm8MQ0glpEGEAIKlwEKkgEIDRAAGocBMIGEAoGBAIUzbxOknXf_rNt17_ir8JlWvrtnCWsQd1MAnl5mgArvavDtKeBYHzi5_Ak7DHlLzuA6YE8W175FxLFKpN2hkz-l-M7ltUSd8N1BvJRhK4t6WffWfC_1wPyoAbeSN2Yb1jygtZJQ8wGoXHcJQUXiMit3eFNyylwsJFj1gzAR4JCdIJeRBhABCpYBCpEBCA4QABqGATCBgwKBgFMcbEpl9ukVR6AO_R6sMyiU11I8b8MBSUCEC15iKsrVO8v_m47_TRRjWPYtQ9eZ7o1ocNJHaGUU7qqInFqtFaVnIceP6NmCsXhjs3MLrWPS8IRAy4Zf4FKmGOx3N9O2vemjUygZ9vUiSkULdVrecinRaT8JQ5RG4bUMY04XGIwFIJiRBhADCpYBCpEBCA8QABqGATCBgwKBgGpCkW-NR3li8GlRvqpq2YZGSIgm_PTyDI2Zwfw69grsBmPpVFW48Vw7xoMN35zcrojEpialB_uQzlpLYOvsMl634CRIuj-n1QE3-gaZTTTE8mg-AR4mcxnTKThPnRQpbuOlYAnriwiasWiQEMbGjq_HmWioYYxFo9USlklQn4-9IJmRBhAEEpUBCpIBCAYQABqHATCBhAKBgQCoZkFGm9oLTqjeXZAq6j5S6i7K20V0lNdBBLqfmFBIRuTkYxhs4vUYnWjZrKRAd5bp6_py0csmFmpl_5Yh0b-2pdo_E5PNP7LGRzKyKSiFddyykKKzVOazH8YYldDAfE8Z5HoS9e48an5JsPg0jr-TPu34DnJq3yv2a6dqiKL9zSCakQYSlQEKkgEIEBAAGocBMIGEAoGBALhrihbf3EpjDQS2sCQHazoFgN0nBbE9eesnnFTfzQELXb2gnJU9enmV_aDqaHKjgtLIPpCgn40lHrn5k6mvH5OdedyI6cCzE-N-GFp3nAq0NDJyMe0fhtIRD__CbT0ulcvkeow65ubXWfw6dBC2gR_34rdMe_L_TGRLMWjDULbNIJ"; + static const TString MALFORMED_TVM_SECRET = "adcvxcv./-+"; + static const TTvmId NOT_OUR_ID = 27; + static const TTvmId OUR_ID = 28; + static const TString SECRET = "GRMJrKnj4fOVnvOqe-WyD1"; + static const TString SERVICE_TICKET_PROTOBUF = "CBAQ__________9_IhkI5QEQHBoIYmI6c2VzczEaCGJiOnNlc3My"; + static const TTvmId SRC_ID = 229; + static const TString UNSUPPORTED_VERSION_SERVICE_TICKET = "2:serv:CBAQ__________9_IhkI5QEQHBoIYmI6c2VzczEaCGJiOnNlc3My:WUPx1cTf05fjD1exB35T5j2DCHWH1YaLJon_a4rN-D7JfXHK1Ai4wM4uSfboHD9xmGQH7extqtlEk1tCTCGm5qbRVloJwWzCZBXo3zKX6i1oBYP_89WcjCNPVe1e8jwGdLsnu6PpxL5cn0xCksiStILH5UmDR6xfkJdnmMG94o8"; + static const TString VALID_SERVICE_TICKET_1 = "3:serv:CBAQ__________9_IhkI5QEQHBoIYmI6c2VzczEaCGJiOnNlc3My:WUPx1cTf05fjD1exB35T5j2DCHWH1YaLJon_a4rN-D7JfXHK1Ai4wM4uSfboHD9xmGQH7extqtlEk1tCTCGm5qbRVloJwWzCZBXo3zKX6i1oBYP_89WcjCNPVe1e8jwGdLsnu6PpxL5cn0xCksiStILH5UmDR6xfkJdnmMG94o8"; + static const TString VALID_SERVICE_TICKET_2 = "3:serv:CBAQ__________9_IskICOUBEBwaCGJiOnNlc3MxGgliYjpzZXNzMTAaCmJiOnNlc3MxMDAaCWJiOnNlc3MxMRoJYmI6c2VzczEyGgliYjpzZXNzMTMaCWJiOnNlc3MxNBoJYmI6c2VzczE1GgliYjpzZXNzMTYaCWJiOnNlc3MxNxoJYmI6c2VzczE4GgliYjpzZXNzMTkaCGJiOnNlc3MyGgliYjpzZXNzMjAaCWJiOnNlc3MyMRoJYmI6c2VzczIyGgliYjpzZXNzMjMaCWJiOnNlc3MyNBoJYmI6c2VzczI1GgliYjpzZXNzMjYaCWJiOnNlc3MyNxoJYmI6c2VzczI4GgliYjpzZXNzMjkaCGJiOnNlc3MzGgliYjpzZXNzMzAaCWJiOnNlc3MzMRoJYmI6c2VzczMyGgliYjpzZXNzMzMaCWJiOnNlc3MzNBoJYmI6c2VzczM1GgliYjpzZXNzMzYaCWJiOnNlc3MzNxoJYmI6c2VzczM4GgliYjpzZXNzMzkaCGJiOnNlc3M0GgliYjpzZXNzNDAaCWJiOnNlc3M0MRoJYmI6c2VzczQyGgliYjpzZXNzNDMaCWJiOnNlc3M0NBoJYmI6c2VzczQ1GgliYjpzZXNzNDYaCWJiOnNlc3M0NxoJYmI6c2VzczQ4GgliYjpzZXNzNDkaCGJiOnNlc3M1GgliYjpzZXNzNTAaCWJiOnNlc3M1MRoJYmI6c2VzczUyGgliYjpzZXNzNTMaCWJiOnNlc3M1NBoJYmI6c2VzczU1GgliYjpzZXNzNTYaCWJiOnNlc3M1NxoJYmI6c2VzczU4GgliYjpzZXNzNTkaCGJiOnNlc3M2GgliYjpzZXNzNjAaCWJiOnNlc3M2MRoJYmI6c2VzczYyGgliYjpzZXNzNjMaCWJiOnNlc3M2NBoJYmI6c2VzczY1GgliYjpzZXNzNjYaCWJiOnNlc3M2NxoJYmI6c2VzczY4GgliYjpzZXNzNjkaCGJiOnNlc3M3GgliYjpzZXNzNzAaCWJiOnNlc3M3MRoJYmI6c2VzczcyGgliYjpzZXNzNzMaCWJiOnNlc3M3NBoJYmI6c2Vzczc1GgliYjpzZXNzNzYaCWJiOnNlc3M3NxoJYmI6c2Vzczc4GgliYjpzZXNzNzkaCGJiOnNlc3M4GgliYjpzZXNzODAaCWJiOnNlc3M4MRoJYmI6c2VzczgyGgliYjpzZXNzODMaCWJiOnNlc3M4NBoJYmI6c2Vzczg1GgliYjpzZXNzODYaCWJiOnNlc3M4NxoJYmI6c2Vzczg4GgliYjpzZXNzODkaCGJiOnNlc3M5GgliYjpzZXNzOTAaCWJiOnNlc3M5MRoJYmI6c2VzczkyGgliYjpzZXNzOTMaCWJiOnNlc3M5NBoJYmI6c2Vzczk1GgliYjpzZXNzOTYaCWJiOnNlc3M5NxoJYmI6c2Vzczk4GgliYjpzZXNzOTk:JYmABAVLM6y7_T4n1pRcwBfwDfzMV4JJ3cpbEG617zdGgKRZwL7MalsYn5bq1F2ibujMrsF9nzZf8l4s_e-Ivjkz_xu4KMzSp-pUh9V7XIF_smj0WHYpv6gOvWNuK8uIvlZTTKwtQX0qZOL9m-MEeZiHoQPKZGCfJ_qxMUp-J8I"; + static const TString VALID_SERVICE_TICKET_3 = "3:serv:CBAQ__________9_IgUI5QEQHA:Sd6tmA1CNy2Nf7XevC3x7zr2DrGNRmcl-TxUsDtDW2xI3YXyCxBltWeg0-KtDlqyYuPOP5Jd_-XXNA12KlOPnNzrz3jm-5z8uQl6CjCcrVHUHJ75pGC8r9UOlS8cOgeXQB5dYP-fOWyo5CNadlozx1S2meCIxncbQRV1kCBi4KU"; + static const TString VALID_SERVICE_TICKET_ISSUER = "3:serv:CBAQ__________9_IgsI5QEQHCDr1MT4Ag:Gu66XJT_nKnIRJjFy1561wFhIqkJItcSTGftLo7Yvi7i5wIdV-QuKT_-IMPpgjxnnGbt1Dy3Ys2TEoeJAb0TdaCYG1uy3vpoLONmTx9AenN5dx1HHf46cypLK5D3OdiTjxvqI9uGmSIKrSdRxU8gprpu5QiBDPZqVCWhM60FVSY"; + + Y_UNIT_TEST(ContextExceptionsTest) { + UNIT_ASSERT_EXCEPTION(TServiceContext::TImpl(SECRET, OUR_ID, MALFORMED_TVM_KEYS), TMalformedTvmKeysException); + UNIT_ASSERT_EXCEPTION(TServiceContext::TImpl(SECRET, OUR_ID, EMPTY_TVM_KEYS), TEmptyTvmKeysException); + UNIT_ASSERT_EXCEPTION(TServiceContext::TImpl(MALFORMED_TVM_SECRET, OUR_ID, NUnittest::TVMKNIFE_PUBLIC_KEYS), TMalformedTvmSecretException); + } + + Y_UNIT_TEST(ContextSignTest) { + TServiceContext::TImpl context(SECRET, OUR_ID, NUnittest::TVMKNIFE_PUBLIC_KEYS); + UNIT_ASSERT_VALUES_EQUAL( + "NsPTYak4Cfk-4vgau5lab3W4GPiTtb2etuj3y4MDPrk", + context.SignCgiParamsForTvm(IntToString<10>(std::numeric_limits<time_t>::max()), "13,28", "")); + } + + Y_UNIT_TEST(Ticket1Test) { + TServiceContext::TImpl context(SECRET, OUR_ID, NUnittest::TVMKNIFE_PUBLIC_KEYS); + auto checkedTicket = context.Check(VALID_SERVICE_TICKET_1); + UNIT_ASSERT_EQUAL(ETicketStatus::Ok, checkedTicket->GetStatus()); + UNIT_ASSERT_EQUAL(std::numeric_limits<time_t>::max(), checkedTicket->GetExpirationTime()); + UNIT_ASSERT_EQUAL(SRC_ID, checkedTicket->GetSrc()); + UNIT_ASSERT_EQUAL(TScopes({"bb:sess1", "bb:sess2"}), checkedTicket->GetScopes()); + UNIT_ASSERT(checkedTicket->HasScope("bb:sess1")); + UNIT_ASSERT(checkedTicket->HasScope("bb:sess2")); + UNIT_ASSERT(!checkedTicket->HasScope("bb:sess3")); + UNIT_ASSERT_EQUAL("ticket_type=serv;expiration_time=9223372036854775807;src=229;dst=28;scope=bb:sess1;scope=bb:sess2;", checkedTicket->DebugInfo()); + UNIT_ASSERT(!checkedTicket->GetIssuerUid()); + } + + Y_UNIT_TEST(Ticket2Test) { + TServiceContext::TImpl context(SECRET, OUR_ID, NUnittest::TVMKNIFE_PUBLIC_KEYS); + auto checkedTicket = context.Check(VALID_SERVICE_TICKET_2); + UNIT_ASSERT_EQUAL(ETicketStatus::Ok, checkedTicket->GetStatus()); + UNIT_ASSERT_VALUES_EQUAL("ticket_type=serv;expiration_time=9223372036854775807;src=229;dst=28;scope=bb:sess1;scope=bb:sess10;scope=bb:sess100;scope=bb:sess11;scope=bb:sess12;scope=bb:sess13;scope=bb:sess14;scope=bb:sess15;scope=bb:sess16;scope=bb:sess17;scope=bb:sess18;scope=bb:sess19;scope=bb:sess2;scope=bb:sess20;scope=bb:sess21;scope=bb:sess22;scope=bb:sess23;scope=bb:sess24;scope=bb:sess25;scope=bb:sess26;scope=bb:sess27;scope=bb:sess28;scope=bb:sess29;scope=bb:sess3;scope=bb:sess30;scope=bb:sess31;scope=bb:sess32;scope=bb:sess33;scope=bb:sess34;scope=bb:sess35;scope=bb:sess36;scope=bb:sess37;scope=bb:sess38;scope=bb:sess39;scope=bb:sess4;scope=bb:sess40;scope=bb:sess41;scope=bb:sess42;scope=bb:sess43;scope=bb:sess44;scope=bb:sess45;scope=bb:sess46;scope=bb:sess47;scope=bb:sess48;scope=bb:sess49;scope=bb:sess5;scope=bb:sess50;scope=bb:sess51;scope=bb:sess52;scope=bb:sess53;scope=bb:sess54;scope=bb:sess55;scope=bb:sess56;scope=bb:sess57;scope=bb:sess58;scope=bb:sess59;scope=bb:sess6;scope=bb:sess60;scope=bb:sess61;scope=bb:sess62;scope=bb:sess63;scope=bb:sess64;scope=bb:sess65;scope=bb:sess66;scope=bb:sess67;scope=bb:sess68;scope=bb:sess69;scope=bb:sess7;scope=bb:sess70;scope=bb:sess71;scope=bb:sess72;scope=bb:sess73;scope=bb:sess74;scope=bb:sess75;scope=bb:sess76;scope=bb:sess77;scope=bb:sess78;scope=bb:sess79;scope=bb:sess8;scope=bb:sess80;scope=bb:sess81;scope=bb:sess82;scope=bb:sess83;scope=bb:sess84;scope=bb:sess85;scope=bb:sess86;scope=bb:sess87;scope=bb:sess88;scope=bb:sess89;scope=bb:sess9;scope=bb:sess90;scope=bb:sess91;scope=bb:sess92;scope=bb:sess93;scope=bb:sess94;scope=bb:sess95;scope=bb:sess96;scope=bb:sess97;scope=bb:sess98;scope=bb:sess99;", checkedTicket->DebugInfo()); + UNIT_ASSERT(!checkedTicket->GetIssuerUid()); + } + + Y_UNIT_TEST(Ticket3Test) { + TServiceContext::TImpl context(SECRET, OUR_ID, NUnittest::TVMKNIFE_PUBLIC_KEYS); + auto checkedTicket = context.Check(VALID_SERVICE_TICKET_3); + UNIT_ASSERT_EQUAL(ETicketStatus::Ok, checkedTicket->GetStatus()); + UNIT_ASSERT_VALUES_EQUAL("ticket_type=serv;expiration_time=9223372036854775807;src=229;dst=28;", checkedTicket->DebugInfo()); + UNIT_ASSERT(!checkedTicket->GetIssuerUid()); + } + + Y_UNIT_TEST(TicketIssuerTest) { + TServiceContext::TImpl context(SECRET, OUR_ID, NUnittest::TVMKNIFE_PUBLIC_KEYS); + auto checkedTicket = context.Check(VALID_SERVICE_TICKET_ISSUER); + UNIT_ASSERT_EQUAL(ETicketStatus::Ok, checkedTicket->GetStatus()); + UNIT_ASSERT_VALUES_EQUAL("ticket_type=serv;expiration_time=9223372036854775807;src=229;dst=28;issuer_uid=789654123;", + checkedTicket->DebugInfo()); + UNIT_ASSERT(checkedTicket->GetIssuerUid()); + UNIT_ASSERT_VALUES_EQUAL(789654123, *checkedTicket->GetIssuerUid()); + } + + Y_UNIT_TEST(TicketErrorsTest) { + TServiceContext::TImpl context(SECRET, NOT_OUR_ID, NUnittest::TVMKNIFE_PUBLIC_KEYS); + auto checkedTicket1 = context.Check(VALID_SERVICE_TICKET_1); + UNIT_ASSERT_EQUAL(ETicketStatus::InvalidDst, checkedTicket1->GetStatus()); + + auto checkedTicket2 = context.Check(UNSUPPORTED_VERSION_SERVICE_TICKET); + UNIT_ASSERT_EQUAL(ETicketStatus::UnsupportedVersion, checkedTicket2->GetStatus()); + + auto checkedTicket3 = context.Check(EXPIRED_SERVICE_TICKET); + UNIT_ASSERT_EQUAL(ETicketStatus::Expired, checkedTicket3->GetStatus()); + } + + Y_UNIT_TEST(TicketExceptionTest) { + TServiceContext::TImpl context(SECRET, OUR_ID, NUnittest::TVMKNIFE_PUBLIC_KEYS); + + auto checkedTicket = context.Check(EXPIRED_SERVICE_TICKET); + UNIT_ASSERT_EQUAL(ETicketStatus::Expired, checkedTicket->GetStatus()); + + UNIT_ASSERT_EXCEPTION(checkedTicket->GetScopes(), TNotAllowedException); + UNIT_ASSERT_EXCEPTION(checkedTicket->GetSrc(), TNotAllowedException); + UNIT_ASSERT_EXCEPTION(checkedTicket->HasScope(""), TNotAllowedException); + UNIT_ASSERT_NO_EXCEPTION(bool(*checkedTicket)); + UNIT_ASSERT_NO_EXCEPTION(checkedTicket->DebugInfo()); + } + + Y_UNIT_TEST(TicketProtoTest) { + ticket2::Ticket protobufTicket; + UNIT_ASSERT(protobufTicket.ParseFromString(NUtils::Base64url2bin(SERVICE_TICKET_PROTOBUF))); + TTestServiceTicketImpl checkedTicket(ETicketStatus::Ok, std::move(protobufTicket)); + UNIT_ASSERT_EQUAL(ETicketStatus::Ok, checkedTicket.GetStatus()); + UNIT_ASSERT_VALUES_EQUAL(std::numeric_limits<time_t>::max(), checkedTicket.GetExpirationTime()); + UNIT_ASSERT_EQUAL(SRC_ID, checkedTicket.GetSrc()); + } + + Y_UNIT_TEST(ResetKeysTest) { + TServiceContext::TImpl context(SECRET, OUR_ID, NUnittest::TVMKNIFE_PUBLIC_KEYS); + context.ResetKeys(NUnittest::TVMKNIFE_PUBLIC_KEYS); + auto checkedTicket = context.Check(VALID_SERVICE_TICKET_1); + UNIT_ASSERT_EQUAL(ETicketStatus::Ok, checkedTicket->GetStatus()); + } + + Y_UNIT_TEST(CreateTicketForTests) { + TCheckedServiceTicket t = NTvmAuth::NUnittest::CreateServiceTicket(ETicketStatus::Ok, 42); + UNIT_ASSERT_EQUAL(ETicketStatus::Ok, t.GetStatus()); + UNIT_ASSERT_EQUAL(42, t.GetSrc()); + UNIT_ASSERT_VALUES_EQUAL("ticket_type=serv;src=42;dst=100500;", t.DebugInfo()); + } + + Y_UNIT_TEST(CreateForTests) { + auto t = TCheckedServiceTicket::TImpl::CreateTicketForTests(ETicketStatus::Ok, 456, {}); + UNIT_ASSERT_VALUES_EQUAL(ETicketStatus::Ok, t->GetStatus()); + UNIT_ASSERT_VALUES_EQUAL(456, t->GetSrc()); + UNIT_ASSERT(!t->GetIssuerUid()); + + t = TCheckedServiceTicket::TImpl::CreateTicketForTests(ETicketStatus::Ok, 456, 100800); + UNIT_ASSERT_VALUES_EQUAL(ETicketStatus::Ok, t->GetStatus()); + UNIT_ASSERT_VALUES_EQUAL(456, t->GetSrc()); + UNIT_ASSERT(t->GetIssuerUid()); + UNIT_ASSERT_VALUES_EQUAL(*t->GetIssuerUid(), 100800); + + t = TCheckedServiceTicket::TImpl::CreateTicketForTests(ETicketStatus::Expired, 456, {}); + UNIT_ASSERT_VALUES_EQUAL(ETicketStatus::Expired, t->GetStatus()); + UNIT_ASSERT_EXCEPTION_CONTAINS(t->GetSrc(), TNotAllowedException, "Method cannot be used in non-valid ticket"); + UNIT_ASSERT_EXCEPTION_CONTAINS(t->GetIssuerUid(), TNotAllowedException, "Method cannot be used in non-valid ticket"); + } +} diff --git a/library/cpp/tvmauth/src/ut/user_ut.cpp b/library/cpp/tvmauth/src/ut/user_ut.cpp new file mode 100644 index 0000000000..c040e94974 --- /dev/null +++ b/library/cpp/tvmauth/src/ut/user_ut.cpp @@ -0,0 +1,216 @@ +#include <library/cpp/tvmauth/src/user_impl.h> +#include <library/cpp/tvmauth/src/utils.h> + +#include <library/cpp/tvmauth/exception.h> +#include <library/cpp/tvmauth/unittest.h> + +#include <library/cpp/testing/unittest/registar.h> + +using namespace NTvmAuth; + +Y_UNIT_TEST_SUITE(UserTestSuite) { + Y_UNIT_TEST_DECLARE(TicketProtoTest); +} + +class TTestUserTicketImpl: TCheckedUserTicket::TImpl { + using TCheckedUserTicket::TImpl::TImpl; + Y_UNIT_TEST_FRIEND(UserTestSuite, TicketProtoTest); +}; + +Y_UNIT_TEST_SUITE_IMPLEMENTATION(UserTestSuite) { + static const TString EMPTY_TVM_KEYS = "1:EpUBCpIBCAYQABqHATCBhAKBgQCoZkFGm9oLTqjeXZAq6j5S6i7K20V0lNdBBLqfmFBIRuTkYxhs4vUYnWjZrKRAd5bp6_py0csmFmpl_5Yh0b-2pdo_E5PNP7LGRzKyKSiFddyykKKzVOazH8YYldDAfE8Z5HoS9e48an5JsPg0jr-TPu34DnJq3yv2a6dqiKL9zSCakQY"; + static const TString EXPIRED_USER_TICKET = "3:user:CA0QABokCgMIyAMKAgh7EMgDGghiYjpzZXNzMRoIYmI6c2VzczIgEigB:D0CmYVwWg91LDYejjeQ2UP8AeiA_mr1q1CUD_lfJ9zQSEYEOYGDTafg4Um2rwOOvQnsD1JHM4zHyMUJ6Jtp9GAm5pmhbXBBZqaCcJpyxLTEC8a81MhJFCCJRvu_G1FiAgRgB25gI3HIbkvHFUEqAIC_nANy7NFQnbKk2S-EQPGY"; + static const TString MALFORMED_TVM_KEYS = "1:CpgCCpMCCAEQABqIAjCCAQQCggEAcLEXeH67FQESFUn4_7wnX7wN0PUrBoUsm3QQ4W5vC-qz6sXaEjSwnTV8w1o-z6X9KPLlhzMQvuS38NCNfK4uvJ4Zvfp3YsXJ25-rYtbnrYJHNvHohD-kPCCw_yZpMp21JdWigzQGuV7CtrxUhF-NNrsnUaJrE5-OpEWNt4X6nCItKIYeVcSK6XJUbEWbrNCRbvkSc4ak2ymFeMuHYJVjxh4eQbk7_ZPzodP0WvF6eUYrYeb42imVEOR8ofVLQWE5DVnb1z_TqZm4i1XkS7jMwZuBxBRw8DGdYei0lT_sAf7KST2jC0590NySB3vsBgWEVs1OdUUWA6r-Dvx9dsOQtSCVkQYQAAqZAgqUAggCEAAaiQIwggEFAoIBAQDhEBM5-6YsPWfogKtbluJoCX1WV2KdzOaQ0-OlRbBzeCzw-eQKu12c8WakHBbeCMd1I1TU64SDkDorWjXGIa_2xT6N3zzNAE50roTbPCcmeQrps26woTYfYIuqDdoxYKZNr0lvNLLW47vBr7EKqo1S4KSj7aXK_XYeEvUgIgf3nVIcNrio7VTnFmGGVQCepaL1Hi1gN4yIXjVZ06PBPZ-DxSRu6xOGbFrfKMJeMPs7KOyE-26Q3xOXdTIa1X-zYIucTd_bxUCL4BVbwW2AvbbFsaG7ISmVdGu0XUTmhXs1KrEfUVLRJhE4Dx99hAZXm1_HlYMUeJcMQ_oHOhV94ENFIJaRBhACCpYBCpEBCAMQABqGATCBgwKBgF9t2YJGAJkRRFq6fWhi3m1TFW1UOE0f6ZrfYhHAkpqGlKlh0QVfeTNPpeJhi75xXzCe6oReRUm-0DbqDNhTShC7uGUv1INYnRBQWH6E-5Fc5XrbDFSuGQw2EYjNfHy_HefHJXxQKAqPvxBDKMKkHgV58WtM6rC8jRi9sdX_ig2NIJeRBhABCpYBCpEBCAQQABqGATCBgwKBgGB4d6eLGUBv-Q6EPLehC4S-yuE2HB-_rJ7WkeYwyp-xIPolPrd-PQme2utHB4ZgpXHIu_OFksDe_0bPgZniNRSVRbl7W49DgS5Ya3kMfrYB4DnF5Fta5tn1oV6EwxYD4JONpFTenOJALPGTPawxXEfon_peiHOSBuQMu3_Vn-l1IJiRBhADCpcBCpIBCAUQABqHATCBhAKBgQCTJMKIfmfeZpaI7Q9rnsc29gdWawK7TnpVKRHws1iY7EUlYROeVcMdAwEqVM6f8BVCKLGgzQ7Gar_uuxfUGKwqEQzoppDraw4F75J464-7D5f6_oJQuGIBHZxqbMONtLjBCXRUhQW5szBLmTQ_R3qaJb5vf-h0APZfkYhq1cTttSCZkQYQBAqWAQqRAQgLEAAahgEwgYMCgYBvvGVH_M2H8qxxv94yaDYUTWbRnJ1uiIYc59KIQlfFimMPhSS7x2tqUa2-hI55JiII0Xym6GNkwLhyc1xtWChpVuIdSnbvttbrt4weDMLHqTwNOF6qAsVKGKT1Yh8yf-qb-DSmicgvFc74mBQm_6gAY1iQsf33YX8578ClhKBWHSCVkQYQAAqXAQqSAQgMEAAahwEwgYQCgYEAkuzFcd5TJu7lYWYe2hQLFfUWIIj91BvQQLa_Thln4YtGCO8gG1KJqJm-YlmJOWQG0B7H_5RVhxUxV9KpmFnsDVkzUFKOsCBaYGXc12xPVioawUlAwp5qp3QQtZyx_se97YIoLzuLr46UkLcLnkIrp-Jo46QzYi_QHq45WTm8MQ0glpEGEAIKlwEKkgEIDRAAGocBMIGEAoGBAIUzbxOknXf_rNt17_ir8JlWvrtnCWsQd1MAnl5mgArvavDtKeBYHzi5_Ak7DHlLzuA6YE8W175FxLFKpN2hkz-l-M7ltUSd8N1BvJRhK4t6WffWfC_1wPyoAbeSN2Yb1jygtZJQ8wGoXHcJQUXiMit3eFNyylwsJFj1gzAR4JCdIJeRBhABCpYBCpEBCA4QABqGATCBgwKBgFMcbEpl9ukVR6AO_R6sMyiU11I8b8MBSUCEC15iKsrVO8v_m47_TRRjWPYtQ9eZ7o1ocNJHaGUU7qqInFqtFaVnIceP6NmCsXhjs3MLrWPS8IRAy4Zf4FKmGOx3N9O2vemjUygZ9vUiSkULdVrecinRaT8JQ5RG4bUMY04XGIwFIJiRBhADCpYBCpEBCA8QABqGATCBgwKBgGpCkW-NR3li8GlRvqpq2YZGSIgm_PTyDI2Zwfw69grsBmPpVFW48Vw7xoMN35zcrojEpialB_uQzlpLYOvsMl634CRIuj-n1QE3-gaZTTTE8mg-AR4mcxnTKThPnRQpbuOlYAnriwiasWiQEMbGjq_HmWioYYxFo9USlklQn4-9IJmRBhAEEpUBCpIBCAYQABqHATCBhAKBgQCoZkFGm9oLTqjeXZAq6j5S6i7K20V0lNdBBLqfmFBIRuTkYxhs4vUYnWjZrKRAd5bp6_py0csmFmpl_5Yh0b-2pdo_E5PNP7LGRzKyKSiFddyykKKzVOazH8YYldDAfE8Z5HoS9e48an5JsPg0jr-TPu34DnJq3yv2a6dqiKL9zSCakQYSlQEKkgEIEBAAGocBMIGEAoGBALhrihbf3EpjDQS2sCQHazoFgN0nBbE9eesnnFTfzQELXb2gnJU9enmV_aDqaHKjgtLIPpCgn40lHrn5k6mvH5OdedyI6cCzE-N-GFp3nAq0NDJyMe0fhtIRD__CbT0ulcvkeow65ubXWfw6dBC2gR_34rdMe_L_TGRLMWjDULbNIJ"; + static const TString UNSUPPORTED_VERSION_USER_TICKET = "2:user:CA0Q__________9_GiQKAwjIAwoCCHsQyAMaCGJiOnNlc3MxGghiYjpzZXNzMiASKAE:KJFv5EcXn9krYk19LCvlFrhMW-R4q8mKfXJXCd-RBVBgUQzCOR1Dx2FiOyU-BxUoIsaU0PiwTjbVY5I2onJDilge70Cl5zEPI9pfab2qwklACq_ZBUvD1tzrfNUr88otBGAziHASJWgyVDkhyQ3p7YbN38qpb0vGQrYNxlk4e2I"; + static const TString USER_TICKET_PROTOBUF = "CA0Q__________9_GiQKAwjIAwoCCHsQyAMaCGJiOnNlc3MxGghiYjpzZXNzMiASKAE"; + static const TString VALID_USER_TICKET_1 = "3:user:CA0Q__________9_GiQKAwjIAwoCCHsQyAMaCGJiOnNlc3MxGghiYjpzZXNzMiASKAE:KJFv5EcXn9krYk19LCvlFrhMW-R4q8mKfXJXCd-RBVBgUQzCOR1Dx2FiOyU-BxUoIsaU0PiwTjbVY5I2onJDilge70Cl5zEPI9pfab2qwklACq_ZBUvD1tzrfNUr88otBGAziHASJWgyVDkhyQ3p7YbN38qpb0vGQrYNxlk4e2I"; + static const TString VALID_USER_TICKET_2 = "3:user:CA0Q__________9_GhAKAwjIAwoCCHsQyAMgEigB:KRibGYTJUA2ns0Fn7VYqeMZ1-GdscB1o9pRzELyr7QJrJsfsE8Y_HoVvB8Npr-oalv6AXOpagSc8HpZjAQz8zKMAVE_tI0tL-9DEsHirpawEbpy7OWV7-k18o1m-RaDaKeTlIB45KHbBul1-9aeKkortBfbbXtz_Qy9r_mfFPiQ"; + static const TString VALID_USER_TICKET_3 = "3:user:CA0Q__________9_Go8bCgIIAAoCCAEKAggCCgIIAwoCCAQKAggFCgIIBgoCCAcKAggICgIICQoCCAoKAggLCgIIDAoCCA0KAggOCgIIDwoCCBAKAggRCgIIEgoCCBMKAggUCgIIFQoCCBYKAggXCgIIGAoCCBkKAggaCgIIGwoCCBwKAggdCgIIHgoCCB8KAgggCgIIIQoCCCIKAggjCgIIJAoCCCUKAggmCgIIJwoCCCgKAggpCgIIKgoCCCsKAggsCgIILQoCCC4KAggvCgIIMAoCCDEKAggyCgIIMwoCCDQKAgg1CgIINgoCCDcKAgg4CgIIOQoCCDoKAgg7CgIIPAoCCD0KAgg-CgIIPwoCCEAKAghBCgIIQgoCCEMKAghECgIIRQoCCEYKAghHCgIISAoCCEkKAghKCgIISwoCCEwKAghNCgIITgoCCE8KAghQCgIIUQoCCFIKAghTCgIIVAoCCFUKAghWCgIIVwoCCFgKAghZCgIIWgoCCFsKAghcCgIIXQoCCF4KAghfCgIIYAoCCGEKAghiCgIIYwoCCGQKAghlCgIIZgoCCGcKAghoCgIIaQoCCGoKAghrCgIIbAoCCG0KAghuCgIIbwoCCHAKAghxCgIIcgoCCHMKAgh0CgIIdQoCCHYKAgh3CgIIeAoCCHkKAgh6CgIIewoCCHwKAgh9CgIIfgoCCH8KAwiAAQoDCIEBCgMIggEKAwiDAQoDCIQBCgMIhQEKAwiGAQoDCIcBCgMIiAEKAwiJAQoDCIoBCgMIiwEKAwiMAQoDCI0BCgMIjgEKAwiPAQoDCJABCgMIkQEKAwiSAQoDCJMBCgMIlAEKAwiVAQoDCJYBCgMIlwEKAwiYAQoDCJkBCgMImgEKAwibAQoDCJwBCgMInQEKAwieAQoDCJ8BCgMIoAEKAwihAQoDCKIBCgMIowEKAwikAQoDCKUBCgMIpgEKAwinAQoDCKgBCgMIqQEKAwiqAQoDCKsBCgMIrAEKAwitAQoDCK4BCgMIrwEKAwiwAQoDCLEBCgMIsgEKAwizAQoDCLQBCgMItQEKAwi2AQoDCLcBCgMIuAEKAwi5AQoDCLoBCgMIuwEKAwi8AQoDCL0BCgMIvgEKAwi_AQoDCMABCgMIwQEKAwjCAQoDCMMBCgMIxAEKAwjFAQoDCMYBCgMIxwEKAwjIAQoDCMkBCgMIygEKAwjLAQoDCMwBCgMIzQEKAwjOAQoDCM8BCgMI0AEKAwjRAQoDCNIBCgMI0wEKAwjUAQoDCNUBCgMI1gEKAwjXAQoDCNgBCgMI2QEKAwjaAQoDCNsBCgMI3AEKAwjdAQoDCN4BCgMI3wEKAwjgAQoDCOEBCgMI4gEKAwjjAQoDCOQBCgMI5QEKAwjmAQoDCOcBCgMI6AEKAwjpAQoDCOoBCgMI6wEKAwjsAQoDCO0BCgMI7gEKAwjvAQoDCPABCgMI8QEKAwjyAQoDCPMBCgMI9AEKAwj1AQoDCPYBCgMI9wEKAwj4AQoDCPkBCgMI-gEKAwj7AQoDCPwBCgMI_QEKAwj-AQoDCP8BCgMIgAIKAwiBAgoDCIICCgMIgwIKAwiEAgoDCIUCCgMIhgIKAwiHAgoDCIgCCgMIiQIKAwiKAgoDCIsCCgMIjAIKAwiNAgoDCI4CCgMIjwIKAwiQAgoDCJECCgMIkgIKAwiTAgoDCJQCCgMIlQIKAwiWAgoDCJcCCgMImAIKAwiZAgoDCJoCCgMImwIKAwicAgoDCJ0CCgMIngIKAwifAgoDCKACCgMIoQIKAwiiAgoDCKMCCgMIpAIKAwilAgoDCKYCCgMIpwIKAwioAgoDCKkCCgMIqgIKAwirAgoDCKwCCgMIrQIKAwiuAgoDCK8CCgMIsAIKAwixAgoDCLICCgMIswIKAwi0AgoDCLUCCgMItgIKAwi3AgoDCLgCCgMIuQIKAwi6AgoDCLsCCgMIvAIKAwi9AgoDCL4CCgMIvwIKAwjAAgoDCMECCgMIwgIKAwjDAgoDCMQCCgMIxQIKAwjGAgoDCMcCCgMIyAIKAwjJAgoDCMoCCgMIywIKAwjMAgoDCM0CCgMIzgIKAwjPAgoDCNACCgMI0QIKAwjSAgoDCNMCCgMI1AIKAwjVAgoDCNYCCgMI1wIKAwjYAgoDCNkCCgMI2gIKAwjbAgoDCNwCCgMI3QIKAwjeAgoDCN8CCgMI4AIKAwjhAgoDCOICCgMI4wIKAwjkAgoDCOUCCgMI5gIKAwjnAgoDCOgCCgMI6QIKAwjqAgoDCOsCCgMI7AIKAwjtAgoDCO4CCgMI7wIKAwjwAgoDCPECCgMI8gIKAwjzAgoDCPQCCgMI9QIKAwj2AgoDCPcCCgMI-AIKAwj5AgoDCPoCCgMI-wIKAwj8AgoDCP0CCgMI_gIKAwj_AgoDCIADCgMIgQMKAwiCAwoDCIMDCgMIhAMKAwiFAwoDCIYDCgMIhwMKAwiIAwoDCIkDCgMIigMKAwiLAwoDCIwDCgMIjQMKAwiOAwoDCI8DCgMIkAMKAwiRAwoDCJIDCgMIkwMKAwiUAwoDCJUDCgMIlgMKAwiXAwoDCJgDCgMImQMKAwiaAwoDCJsDCgMInAMKAwidAwoDCJ4DCgMInwMKAwigAwoDCKEDCgMIogMKAwijAwoDCKQDCgMIpQMKAwimAwoDCKcDCgMIqAMKAwipAwoDCKoDCgMIqwMKAwisAwoDCK0DCgMIrgMKAwivAwoDCLADCgMIsQMKAwiyAwoDCLMDCgMItAMKAwi1AwoDCLYDCgMItwMKAwi4AwoDCLkDCgMIugMKAwi7AwoDCLwDCgMIvQMKAwi-AwoDCL8DCgMIwAMKAwjBAwoDCMIDCgMIwwMKAwjEAwoDCMUDCgMIxgMKAwjHAwoDCMgDCgMIyQMKAwjKAwoDCMsDCgMIzAMKAwjNAwoDCM4DCgMIzwMKAwjQAwoDCNEDCgMI0gMKAwjTAwoDCNQDCgMI1QMKAwjWAwoDCNcDCgMI2AMKAwjZAwoDCNoDCgMI2wMKAwjcAwoDCN0DCgMI3gMKAwjfAwoDCOADCgMI4QMKAwjiAwoDCOMDCgMI5AMKAwjlAwoDCOYDCgMI5wMKAwjoAwoDCOkDCgMI6gMKAwjrAwoDCOwDCgMI7QMKAwjuAwoDCO8DCgMI8AMKAwjxAwoDCPIDCgMI8wMQyAMaCGJiOnNlc3MxGgliYjpzZXNzMTAaCmJiOnNlc3MxMDAaCWJiOnNlc3MxMRoJYmI6c2VzczEyGgliYjpzZXNzMTMaCWJiOnNlc3MxNBoJYmI6c2VzczE1GgliYjpzZXNzMTYaCWJiOnNlc3MxNxoJYmI6c2VzczE4GgliYjpzZXNzMTkaCGJiOnNlc3MyGgliYjpzZXNzMjAaCWJiOnNlc3MyMRoJYmI6c2VzczIyGgliYjpzZXNzMjMaCWJiOnNlc3MyNBoJYmI6c2VzczI1GgliYjpzZXNzMjYaCWJiOnNlc3MyNxoJYmI6c2VzczI4GgliYjpzZXNzMjkaCGJiOnNlc3MzGgliYjpzZXNzMzAaCWJiOnNlc3MzMRoJYmI6c2VzczMyGgliYjpzZXNzMzMaCWJiOnNlc3MzNBoJYmI6c2VzczM1GgliYjpzZXNzMzYaCWJiOnNlc3MzNxoJYmI6c2VzczM4GgliYjpzZXNzMzkaCGJiOnNlc3M0GgliYjpzZXNzNDAaCWJiOnNlc3M0MRoJYmI6c2VzczQyGgliYjpzZXNzNDMaCWJiOnNlc3M0NBoJYmI6c2VzczQ1GgliYjpzZXNzNDYaCWJiOnNlc3M0NxoJYmI6c2VzczQ4GgliYjpzZXNzNDkaCGJiOnNlc3M1GgliYjpzZXNzNTAaCWJiOnNlc3M1MRoJYmI6c2VzczUyGgliYjpzZXNzNTMaCWJiOnNlc3M1NBoJYmI6c2VzczU1GgliYjpzZXNzNTYaCWJiOnNlc3M1NxoJYmI6c2VzczU4GgliYjpzZXNzNTkaCGJiOnNlc3M2GgliYjpzZXNzNjAaCWJiOnNlc3M2MRoJYmI6c2VzczYyGgliYjpzZXNzNjMaCWJiOnNlc3M2NBoJYmI6c2VzczY1GgliYjpzZXNzNjYaCWJiOnNlc3M2NxoJYmI6c2VzczY4GgliYjpzZXNzNjkaCGJiOnNlc3M3GgliYjpzZXNzNzAaCWJiOnNlc3M3MRoJYmI6c2VzczcyGgliYjpzZXNzNzMaCWJiOnNlc3M3NBoJYmI6c2Vzczc1GgliYjpzZXNzNzYaCWJiOnNlc3M3NxoJYmI6c2Vzczc4GgliYjpzZXNzNzkaCGJiOnNlc3M4GgliYjpzZXNzODAaCWJiOnNlc3M4MRoJYmI6c2VzczgyGgliYjpzZXNzODMaCWJiOnNlc3M4NBoJYmI6c2Vzczg1GgliYjpzZXNzODYaCWJiOnNlc3M4NxoJYmI6c2Vzczg4GgliYjpzZXNzODkaCGJiOnNlc3M5GgliYjpzZXNzOTAaCWJiOnNlc3M5MRoJYmI6c2VzczkyGgliYjpzZXNzOTMaCWJiOnNlc3M5NBoJYmI6c2Vzczk1GgliYjpzZXNzOTYaCWJiOnNlc3M5NxoJYmI6c2Vzczk4GgliYjpzZXNzOTkgEigB:CX8PIOrxJnQqFXl7wAsiHJ_1VGjoI-asNlCXb8SE8jtI2vdh9x6CqbAurSgIlAAEgotVP-nuUR38x_a9YJuXzmG5AvJ458apWQtODHIDIX6ZaIwMxjS02R7S5LNqXa0gAuU_R6bCWpZdWe2uLMkdpu5KHbDgW08g-uaP_nceDOk"; + + Y_UNIT_TEST(ContextText) { + TUserContext::TImpl context(EBlackboxEnv::Prod, NUnittest::TVMKNIFE_PUBLIC_KEYS); + UNIT_ASSERT_EQUAL(2, context.GetKeys().size()); + UNIT_ASSERT_NO_EXCEPTION(context.ResetKeys(NUnittest::TVMKNIFE_PUBLIC_KEYS)); + UNIT_ASSERT_EQUAL(2, context.GetKeys().size()); + } + + Y_UNIT_TEST(ContextEnvTest) { + TUserContext::TImpl p(EBlackboxEnv::Prod, NUnittest::TVMKNIFE_PUBLIC_KEYS); + UNIT_ASSERT_EQUAL(2, p.GetKeys().size()); + UNIT_ASSERT(p.IsAllowed(tvm_keys::Prod)); + UNIT_ASSERT(!p.IsAllowed(tvm_keys::ProdYateam)); + UNIT_ASSERT(!p.IsAllowed(tvm_keys::Test)); + UNIT_ASSERT(!p.IsAllowed(tvm_keys::TestYateam)); + UNIT_ASSERT(!p.IsAllowed(tvm_keys::Stress)); + + TUserContext::TImpl pt(EBlackboxEnv::ProdYateam, NUnittest::TVMKNIFE_PUBLIC_KEYS); + UNIT_ASSERT_EQUAL(2, pt.GetKeys().size()); + UNIT_ASSERT(!pt.IsAllowed(tvm_keys::Prod)); + UNIT_ASSERT(pt.IsAllowed(tvm_keys::ProdYateam)); + UNIT_ASSERT(!pt.IsAllowed(tvm_keys::Test)); + UNIT_ASSERT(!pt.IsAllowed(tvm_keys::TestYateam)); + UNIT_ASSERT(!pt.IsAllowed(tvm_keys::Stress)); + + TUserContext::TImpl t(EBlackboxEnv::Test, NUnittest::TVMKNIFE_PUBLIC_KEYS); + UNIT_ASSERT_EQUAL(2, t.GetKeys().size()); + UNIT_ASSERT(!t.IsAllowed(tvm_keys::Prod)); + UNIT_ASSERT(!t.IsAllowed(tvm_keys::ProdYateam)); + UNIT_ASSERT(t.IsAllowed(tvm_keys::Test)); + UNIT_ASSERT(!t.IsAllowed(tvm_keys::TestYateam)); + UNIT_ASSERT(!t.IsAllowed(tvm_keys::Stress)); + + TUserContext::TImpl tt(EBlackboxEnv::TestYateam, NUnittest::TVMKNIFE_PUBLIC_KEYS); + UNIT_ASSERT_EQUAL(2, tt.GetKeys().size()); + UNIT_ASSERT(!tt.IsAllowed(tvm_keys::Prod)); + UNIT_ASSERT(!tt.IsAllowed(tvm_keys::ProdYateam)); + UNIT_ASSERT(!tt.IsAllowed(tvm_keys::Test)); + UNIT_ASSERT(tt.IsAllowed(tvm_keys::TestYateam)); + UNIT_ASSERT(!tt.IsAllowed(tvm_keys::Stress)); + + TUserContext::TImpl s(EBlackboxEnv::Stress, NUnittest::TVMKNIFE_PUBLIC_KEYS); + UNIT_ASSERT_EQUAL(4, s.GetKeys().size()); + UNIT_ASSERT(s.IsAllowed(tvm_keys::Prod)); + UNIT_ASSERT(!s.IsAllowed(tvm_keys::ProdYateam)); + UNIT_ASSERT(!s.IsAllowed(tvm_keys::Test)); + UNIT_ASSERT(!s.IsAllowed(tvm_keys::TestYateam)); + UNIT_ASSERT(s.IsAllowed(tvm_keys::Stress)); + } + + Y_UNIT_TEST(ContextExceptionsText) { + UNIT_ASSERT_EXCEPTION(TUserContext::TImpl(EBlackboxEnv::Prod, EMPTY_TVM_KEYS), TEmptyTvmKeysException); + UNIT_ASSERT_EXCEPTION(TUserContext::TImpl(EBlackboxEnv::Prod, MALFORMED_TVM_KEYS), TMalformedTvmKeysException); + UNIT_ASSERT_EXCEPTION(TUserContext::TImpl(EBlackboxEnv::Prod, "adcvxcv./-+"), TMalformedTvmKeysException); + } + + Y_UNIT_TEST(Ticket1Test) { + TUserContext::TImpl context(EBlackboxEnv::Test, NUnittest::TVMKNIFE_PUBLIC_KEYS); + auto checkedTicket = context.Check(VALID_USER_TICKET_1); + UNIT_ASSERT_EQUAL(ETicketStatus::Ok, checkedTicket->GetStatus()); + UNIT_ASSERT_EQUAL(std::numeric_limits<time_t>::max(), checkedTicket->GetExpirationTime()); + UNIT_ASSERT_EQUAL(TUids({456, 123}), checkedTicket->GetUids()); + UNIT_ASSERT_EQUAL(456, checkedTicket->GetDefaultUid()); + UNIT_ASSERT_EQUAL(TScopes({"bb:sess1", "bb:sess2"}), checkedTicket->GetScopes()); + UNIT_ASSERT(checkedTicket->HasScope("bb:sess1")); + UNIT_ASSERT(checkedTicket->HasScope("bb:sess2")); + UNIT_ASSERT(!checkedTicket->HasScope("bb:sess3")); + UNIT_ASSERT_EQUAL("ticket_type=user;expiration_time=9223372036854775807;scope=bb:sess1;scope=bb:sess2;default_uid=456;uid=456;uid=123;env=Test;", checkedTicket->DebugInfo()); + } + + Y_UNIT_TEST(Ticket2Test) { + TUserContext::TImpl context(EBlackboxEnv::Test, NUnittest::TVMKNIFE_PUBLIC_KEYS); + auto checkedTicket = context.Check(VALID_USER_TICKET_2); + UNIT_ASSERT_EQUAL(ETicketStatus::Ok, checkedTicket->GetStatus()); + UNIT_ASSERT_VALUES_EQUAL("ticket_type=user;expiration_time=9223372036854775807;default_uid=456;uid=456;uid=123;env=Test;", checkedTicket->DebugInfo()); + } + + Y_UNIT_TEST(Ticket3Test) { + TUserContext::TImpl context(EBlackboxEnv::Test, NUnittest::TVMKNIFE_PUBLIC_KEYS); + auto checkedTicket = context.Check(VALID_USER_TICKET_3); + UNIT_ASSERT_EQUAL(ETicketStatus::Ok, checkedTicket->GetStatus()); + UNIT_ASSERT_VALUES_EQUAL("ticket_type=user;expiration_time=9223372036854775807;scope=bb:sess1;scope=bb:sess10;scope=bb:sess100;scope=bb:sess11;scope=bb:sess12;scope=bb:sess13;scope=bb:sess14;scope=bb:sess15;scope=bb:sess16;scope=bb:sess17;scope=bb:sess18;scope=bb:sess19;scope=bb:sess2;scope=bb:sess20;scope=bb:sess21;scope=bb:sess22;scope=bb:sess23;scope=bb:sess24;scope=bb:sess25;scope=bb:sess26;scope=bb:sess27;scope=bb:sess28;scope=bb:sess29;scope=bb:sess3;scope=bb:sess30;scope=bb:sess31;scope=bb:sess32;scope=bb:sess33;scope=bb:sess34;scope=bb:sess35;scope=bb:sess36;scope=bb:sess37;scope=bb:sess38;scope=bb:sess39;scope=bb:sess4;scope=bb:sess40;scope=bb:sess41;scope=bb:sess42;scope=bb:sess43;scope=bb:sess44;scope=bb:sess45;scope=bb:sess46;scope=bb:sess47;scope=bb:sess48;scope=bb:sess49;scope=bb:sess5;scope=bb:sess50;scope=bb:sess51;scope=bb:sess52;scope=bb:sess53;scope=bb:sess54;scope=bb:sess55;scope=bb:sess56;scope=bb:sess57;scope=bb:sess58;scope=bb:sess59;scope=bb:sess6;scope=bb:sess60;scope=bb:sess61;scope=bb:sess62;scope=bb:sess63;scope=bb:sess64;scope=bb:sess65;scope=bb:sess66;scope=bb:sess67;scope=bb:sess68;scope=bb:sess69;scope=bb:sess7;scope=bb:sess70;scope=bb:sess71;scope=bb:sess72;scope=bb:sess73;scope=bb:sess74;scope=bb:sess75;scope=bb:sess76;scope=bb:sess77;scope=bb:sess78;scope=bb:sess79;scope=bb:sess8;scope=bb:sess80;scope=bb:sess81;scope=bb:sess82;scope=bb:sess83;scope=bb:sess84;scope=bb:sess85;scope=bb:sess86;scope=bb:sess87;scope=bb:sess88;scope=bb:sess89;scope=bb:sess9;scope=bb:sess90;scope=bb:sess91;scope=bb:sess92;scope=bb:sess93;scope=bb:sess94;scope=bb:sess95;scope=bb:sess96;scope=bb:sess97;scope=bb:sess98;scope=bb:sess99;default_uid=456;uid=0;uid=1;uid=2;uid=3;uid=4;uid=5;uid=6;uid=7;uid=8;uid=9;uid=10;uid=11;uid=12;uid=13;uid=14;uid=15;uid=16;uid=17;uid=18;uid=19;uid=20;uid=21;uid=22;uid=23;uid=24;uid=25;uid=26;uid=27;uid=28;uid=29;uid=30;uid=31;uid=32;uid=33;uid=34;uid=35;uid=36;uid=37;uid=38;uid=39;uid=40;uid=41;uid=42;uid=43;uid=44;uid=45;uid=46;uid=47;uid=48;uid=49;uid=50;uid=51;uid=52;uid=53;uid=54;uid=55;uid=56;uid=57;uid=58;uid=59;uid=60;uid=61;uid=62;uid=63;uid=64;uid=65;uid=66;uid=67;uid=68;uid=69;uid=70;uid=71;uid=72;uid=73;uid=74;uid=75;uid=76;uid=77;uid=78;uid=79;uid=80;uid=81;uid=82;uid=83;uid=84;uid=85;uid=86;uid=87;uid=88;uid=89;uid=90;uid=91;uid=92;uid=93;uid=94;uid=95;uid=96;uid=97;uid=98;uid=99;uid=100;uid=101;uid=102;uid=103;uid=104;uid=105;uid=106;uid=107;uid=108;uid=109;uid=110;uid=111;uid=112;uid=113;uid=114;uid=115;uid=116;uid=117;uid=118;uid=119;uid=120;uid=121;uid=122;uid=123;uid=124;uid=125;uid=126;uid=127;uid=128;uid=129;uid=130;uid=131;uid=132;uid=133;uid=134;uid=135;uid=136;uid=137;uid=138;uid=139;uid=140;uid=141;uid=142;uid=143;uid=144;uid=145;uid=146;uid=147;uid=148;uid=149;uid=150;uid=151;uid=152;uid=153;uid=154;uid=155;uid=156;uid=157;uid=158;uid=159;uid=160;uid=161;uid=162;uid=163;uid=164;uid=165;uid=166;uid=167;uid=168;uid=169;uid=170;uid=171;uid=172;uid=173;uid=174;uid=175;uid=176;uid=177;uid=178;uid=179;uid=180;uid=181;uid=182;uid=183;uid=184;uid=185;uid=186;uid=187;uid=188;uid=189;uid=190;uid=191;uid=192;uid=193;uid=194;uid=195;uid=196;uid=197;uid=198;uid=199;uid=200;uid=201;uid=202;uid=203;uid=204;uid=205;uid=206;uid=207;uid=208;uid=209;uid=210;uid=211;uid=212;uid=213;uid=214;uid=215;uid=216;uid=217;uid=218;uid=219;uid=220;uid=221;uid=222;uid=223;uid=224;uid=225;uid=226;uid=227;uid=228;uid=229;uid=230;uid=231;uid=232;uid=233;uid=234;uid=235;uid=236;uid=237;uid=238;uid=239;uid=240;uid=241;uid=242;uid=243;uid=244;uid=245;uid=246;uid=247;uid=248;uid=249;uid=250;uid=251;uid=252;uid=253;uid=254;uid=255;uid=256;uid=257;uid=258;uid=259;uid=260;uid=261;uid=262;uid=263;uid=264;uid=265;uid=266;uid=267;uid=268;uid=269;uid=270;uid=271;uid=272;uid=273;uid=274;uid=275;uid=276;uid=277;uid=278;uid=279;uid=280;uid=281;uid=282;uid=283;uid=284;uid=285;uid=286;uid=287;uid=288;uid=289;uid=290;uid=291;uid=292;uid=293;uid=294;uid=295;uid=296;uid=297;uid=298;uid=299;uid=300;uid=301;uid=302;uid=303;uid=304;uid=305;uid=306;uid=307;uid=308;uid=309;uid=310;uid=311;uid=312;uid=313;uid=314;uid=315;uid=316;uid=317;uid=318;uid=319;uid=320;uid=321;uid=322;uid=323;uid=324;uid=325;uid=326;uid=327;uid=328;uid=329;uid=330;uid=331;uid=332;uid=333;uid=334;uid=335;uid=336;uid=337;uid=338;uid=339;uid=340;uid=341;uid=342;uid=343;uid=344;uid=345;uid=346;uid=347;uid=348;uid=349;uid=350;uid=351;uid=352;uid=353;uid=354;uid=355;uid=356;uid=357;uid=358;uid=359;uid=360;uid=361;uid=362;uid=363;uid=364;uid=365;uid=366;uid=367;uid=368;uid=369;uid=370;uid=371;uid=372;uid=373;uid=374;uid=375;uid=376;uid=377;uid=378;uid=379;uid=380;uid=381;uid=382;uid=383;uid=384;uid=385;uid=386;uid=387;uid=388;uid=389;uid=390;uid=391;uid=392;uid=393;uid=394;uid=395;uid=396;uid=397;uid=398;uid=399;uid=400;uid=401;uid=402;uid=403;uid=404;uid=405;uid=406;uid=407;uid=408;uid=409;uid=410;uid=411;uid=412;uid=413;uid=414;uid=415;uid=416;uid=417;uid=418;uid=419;uid=420;uid=421;uid=422;uid=423;uid=424;uid=425;uid=426;uid=427;uid=428;uid=429;uid=430;uid=431;uid=432;uid=433;uid=434;uid=435;uid=436;uid=437;uid=438;uid=439;uid=440;uid=441;uid=442;uid=443;uid=444;uid=445;uid=446;uid=447;uid=448;uid=449;uid=450;uid=451;uid=452;uid=453;uid=454;uid=455;uid=456;uid=457;uid=458;uid=459;uid=460;uid=461;uid=462;uid=463;uid=464;uid=465;uid=466;uid=467;uid=468;uid=469;uid=470;uid=471;uid=472;uid=473;uid=474;uid=475;uid=476;uid=477;uid=478;uid=479;uid=480;uid=481;uid=482;uid=483;uid=484;uid=485;uid=486;uid=487;uid=488;uid=489;uid=490;uid=491;uid=492;uid=493;uid=494;uid=495;uid=496;uid=497;uid=498;uid=499;env=Test;", checkedTicket->DebugInfo()); + } + + Y_UNIT_TEST(TicketExceptionsTest) { + TUserContext::TImpl contextTest(EBlackboxEnv::Test, NUnittest::TVMKNIFE_PUBLIC_KEYS); + auto checkedTicket1 = contextTest.Check(UNSUPPORTED_VERSION_USER_TICKET); + UNIT_ASSERT_EQUAL(ETicketStatus::UnsupportedVersion, checkedTicket1->GetStatus()); + + auto checkedTicket2 = contextTest.Check(EXPIRED_USER_TICKET); + UNIT_ASSERT_EQUAL(ETicketStatus::Expired, checkedTicket2->GetStatus()); + + TUserContext::TImpl contextProd(EBlackboxEnv::Prod, NUnittest::TVMKNIFE_PUBLIC_KEYS); + auto checkedTicket3 = contextProd.Check(VALID_USER_TICKET_1); + UNIT_ASSERT_EQUAL(ETicketStatus::InvalidBlackboxEnv, checkedTicket3->GetStatus()); + + UNIT_ASSERT_EXCEPTION(checkedTicket3->GetDefaultUid(), TNotAllowedException); + UNIT_ASSERT_EXCEPTION(checkedTicket3->GetUids(), TNotAllowedException); + UNIT_ASSERT_EXCEPTION(checkedTicket3->GetScopes(), TNotAllowedException); + UNIT_ASSERT_EXCEPTION(checkedTicket3->HasScope(""), TNotAllowedException); + UNIT_ASSERT_NO_EXCEPTION(bool(*checkedTicket3)); + UNIT_ASSERT_NO_EXCEPTION(checkedTicket3->DebugInfo()); + UNIT_ASSERT_NO_EXCEPTION(checkedTicket3->GetStatus()); + } + + Y_UNIT_TEST(TicketProtoTest) { + ticket2::Ticket protobufTicket; + UNIT_ASSERT(protobufTicket.ParseFromString(NUtils::Base64url2bin(USER_TICKET_PROTOBUF))); + TTestUserTicketImpl userTicket(ETicketStatus::Ok, std::move(protobufTicket)); + UNIT_ASSERT_EQUAL(ETicketStatus::Ok, userTicket.GetStatus()); + UNIT_ASSERT_EQUAL(std::numeric_limits<time_t>::max(), userTicket.GetExpirationTime()); + UNIT_ASSERT_EQUAL(TUids({456, 123}), userTicket.GetUids()); + UNIT_ASSERT_EQUAL(456, userTicket.GetDefaultUid()); + UNIT_ASSERT_EQUAL(TScopes({"bb:sess1", "bb:sess2"}), userTicket.GetScopes()); + UNIT_ASSERT(userTicket.HasScope("bb:sess1")); + UNIT_ASSERT(userTicket.HasScope("bb:sess2")); + UNIT_ASSERT(!userTicket.HasScope("bb:sess3")); + } + + Y_UNIT_TEST(ResetKeysTest) { + TUserContext::TImpl context(EBlackboxEnv::Test, NUnittest::TVMKNIFE_PUBLIC_KEYS); + context.ResetKeys(NUnittest::TVMKNIFE_PUBLIC_KEYS); + auto checkedTicket = context.Check(VALID_USER_TICKET_1); + UNIT_ASSERT_EQUAL(ETicketStatus::Ok, checkedTicket->GetStatus()); + } + + Y_UNIT_TEST(CreateTicketForTests) { + TCheckedUserTicket t = NTvmAuth::NUnittest::CreateUserTicket(ETicketStatus::Ok, 42, {"qwerty", "omg"}, {43, 55, 47}); + UNIT_ASSERT_EQUAL(ETicketStatus::Ok, t.GetStatus()); + UNIT_ASSERT_EQUAL(42, t.GetDefaultUid()); + UNIT_ASSERT_EQUAL(TUids({42, 43, 47, 55}), t.GetUids()); + UNIT_ASSERT_EQUAL(TScopes({"omg", "qwerty"}), t.GetScopes()); + UNIT_ASSERT_VALUES_EQUAL("ticket_type=user;scope=omg;scope=qwerty;default_uid=42;uid=42;uid=43;uid=47;uid=55;env=Test;", t.DebugInfo()); + } + + Y_UNIT_TEST(CreateForTests) { + TUids uids{456}; + TScopes scopes{"scope1", "scope2", "scope3"}; + TScopes scopesIn{"scope1", "scope2", "scope3", "scope1", ""}; + auto t = TCheckedUserTicket::TImpl::CreateTicketForTests(ETicketStatus::Ok, 456, scopesIn, {}); + UNIT_ASSERT_VALUES_EQUAL(ETicketStatus::Ok, t->GetStatus()); + UNIT_ASSERT_VALUES_EQUAL(456, t->GetDefaultUid()); + UNIT_ASSERT_VALUES_EQUAL(uids, t->GetUids()); + UNIT_ASSERT_VALUES_EQUAL(scopes, t->GetScopes()); + + t = TCheckedUserTicket::TImpl::CreateTicketForTests(ETicketStatus::Ok, 456, scopesIn, {123, 456, 789}); + UNIT_ASSERT_VALUES_EQUAL(ETicketStatus::Ok, t->GetStatus()); + UNIT_ASSERT_VALUES_EQUAL(456, t->GetDefaultUid()); + uids = TUids{123, 456, 789}; + UNIT_ASSERT_VALUES_EQUAL(uids, t->GetUids()); + UNIT_ASSERT_VALUES_EQUAL(scopes, t->GetScopes()); + + t = TCheckedUserTicket::TImpl::CreateTicketForTests(ETicketStatus::Ok, 456, scopesIn, {123, 789}); + UNIT_ASSERT_VALUES_EQUAL(ETicketStatus::Ok, t->GetStatus()); + UNIT_ASSERT_VALUES_EQUAL(456, t->GetDefaultUid()); + uids = TUids{123, 456, 789}; + UNIT_ASSERT_VALUES_EQUAL(uids, t->GetUids()); + UNIT_ASSERT_VALUES_EQUAL(scopes, t->GetScopes()); + + t = TCheckedUserTicket::TImpl::CreateTicketForTests(ETicketStatus::Ok, 0, scopesIn, {123, 789}); + UNIT_ASSERT_VALUES_EQUAL(ETicketStatus::Ok, t->GetStatus()); + UNIT_ASSERT_VALUES_EQUAL(0, t->GetDefaultUid()); + uids = TUids{123, 789}; + UNIT_ASSERT_VALUES_EQUAL(uids, t->GetUids()); + UNIT_ASSERT_VALUES_EQUAL(scopes, t->GetScopes()); + + UNIT_ASSERT_EXCEPTION_CONTAINS(TCheckedUserTicket::TImpl::CreateTicketForTests(ETicketStatus::Ok, 0, scopesIn, {}), + yexception, + "User ticket cannot contain empty uid list"); + UNIT_ASSERT_EXCEPTION_CONTAINS(TCheckedUserTicket::TImpl::CreateTicketForTests(ETicketStatus::Ok, 0, scopesIn, {0}), + yexception, + "User ticket cannot contain empty uid list"); + } +} + +template <> +void Out<NTvmAuth::TUids>(IOutputStream& o, const NTvmAuth::TUids& v) { + for (const auto& uid : v) { + o << uid << ","; + } +} + +template <> +void Out<NTvmAuth::TScopes>(IOutputStream& o, const NTvmAuth::TScopes& v) { + for (const auto& scope : v) { + o << scope << ","; + } +} diff --git a/library/cpp/tvmauth/src/ut/utils_ut.cpp b/library/cpp/tvmauth/src/ut/utils_ut.cpp new file mode 100644 index 0000000000..c9cb81c36f --- /dev/null +++ b/library/cpp/tvmauth/src/ut/utils_ut.cpp @@ -0,0 +1,95 @@ +#include <library/cpp/tvmauth/src/utils.h> + +#include <library/cpp/testing/unittest/registar.h> + +#include <util/generic/maybe.h> + +Y_UNIT_TEST_SUITE(UtilsTestSuite) { + static const TString VALID_SERVICE_TICKET_1 = "3:serv:CBAQ__________9_IhkI5QEQHBoIYmI6c2VzczEaCGJiOnNlc3My:WUPx1cTf05fjD1exB35T5j2DCHWH1YaLJon_a4rN-D7JfXHK1Ai4wM4uSfboHD9xmGQH7extqtlEk1tCTCGm5qbRVloJwWzCZBXo3zKX6i1oBYP_89WcjCNPVe1e8jwGdLsnu6PpxL5cn0xCksiStILH5UmDR6xfkJdnmMG94o8"; + static const TString EXPIRED_SERVICE_TICKET = "3:serv:CBAQACIZCOUBEBwaCGJiOnNlc3MxGghiYjpzZXNzMg:IwfMNJYEqStY_SixwqJnyHOMCPR7-3HHk4uylB2oVRkthtezq-OOA7QizDvx7VABLs_iTlXuD1r5IjufNei_EiV145eaa3HIg4xCdJXCojMexf2UYJz8mF2b0YzFAy6_KWagU7xo13CyKAqzJuQf5MJcSUf0ecY9hVh36cJ51aw"; + using namespace NTvmAuth; + + Y_UNIT_TEST(base64Test) { + UNIT_ASSERT_VALUES_EQUAL("-hHx", NUtils::Bin2base64url("\xfa\x11\xf1")); + UNIT_ASSERT_VALUES_EQUAL("-hHx_g", NUtils::Bin2base64url("\xfa\x11\xf1\xfe")); + UNIT_ASSERT_VALUES_EQUAL("-hHx_v8", NUtils::Bin2base64url("\xfa\x11\xf1\xfe\xff")); + + UNIT_ASSERT_VALUES_EQUAL("", NUtils::Base64url2bin("hHx++")); + UNIT_ASSERT_VALUES_EQUAL("", NUtils::Base64url2bin("&*^")); + UNIT_ASSERT_VALUES_EQUAL("", NUtils::Base64url2bin("")); + UNIT_ASSERT_VALUES_EQUAL("", NUtils::Bin2base64url("")); + + UNIT_ASSERT_VALUES_EQUAL("\xfa\x11\xf1", NUtils::Base64url2bin("-hHx")); + UNIT_ASSERT_VALUES_EQUAL("\xfa\x11\xf1\xfe", NUtils::Base64url2bin("-hHx_g")); + UNIT_ASSERT_VALUES_EQUAL("\xfa\x11\xf1\xfe", NUtils::Base64url2bin("-hHx_g=")); + UNIT_ASSERT_VALUES_EQUAL("\xfa\x11\xf1\xfe", NUtils::Base64url2bin("-hHx_g==")); + UNIT_ASSERT_VALUES_EQUAL("\xfa\x11\xf1\xfe\xff", NUtils::Base64url2bin("-hHx_v8")); + UNIT_ASSERT_VALUES_EQUAL("\xfa\x11\xf1\xfe\xff", NUtils::Base64url2bin("-hHx_v8=")); + + UNIT_ASSERT_VALUES_EQUAL("SGVsbG8sIGV2ZXJ5Ym9keSE", + NUtils::Bin2base64url(("Hello, everybody!"))); + UNIT_ASSERT_VALUES_EQUAL("Hello, everybody!", + NUtils::Base64url2bin(("SGVsbG8sIGV2ZXJ5Ym9keSE"))); + UNIT_ASSERT_VALUES_EQUAL("VGhlIE1hZ2ljIFdvcmRzIGFyZSBTcXVlYW1pc2ggT3NzaWZyYWdl", + NUtils::Bin2base64url(("The Magic Words are Squeamish Ossifrage"))); + UNIT_ASSERT_VALUES_EQUAL("The Magic Words are Squeamish Ossifrage", + NUtils::Base64url2bin(("VGhlIE1hZ2ljIFdvcmRzIGFyZSBTcXVlYW1pc2ggT3NzaWZyYWdl"))); + } + + Y_UNIT_TEST(sign) { + UNIT_ASSERT_VALUES_EQUAL("wkGfeuopf709ozPAeGcDMqtZXPzsWvuNJ1BL586dSug", + NUtils::SignCgiParamsForTvm(NUtils::Base64url2bin("GRMJrKnj4fOVnvOqe-WyD1"), + "1490000000", + "13,19", + "bb:sess,bb:sess2")); + + UNIT_ASSERT_VALUES_EQUAL("HANDYrA4ApQMQ5cfSWZk_InHWJffoXAa57P_X_B5s4M", + NUtils::SignCgiParamsForTvm(NUtils::Base64url2bin("GRMJrKnj4fOasvOqe-WyD1"), + "1490000000", + "13,19", + "bb:sess,bb:sess2")); + + UNIT_ASSERT_VALUES_EQUAL("T-M-3_qtjRM1dR_3hS1CRlHBTZRKK04doHXBJw-5VRk", + NUtils::SignCgiParamsForTvm(NUtils::Base64url2bin("GRMJrKnj4fOasvOqe-WyD1"), + "1490000001", + "13,19", + "bb:sess,bb:sess2")); + + UNIT_ASSERT_VALUES_EQUAL("gwB6M_9Jij50ZADmlDMnoyLc6AhQmtq6MClgGzO1PBE", + NUtils::SignCgiParamsForTvm(NUtils::Base64url2bin("GRMJrKnj4fOasvOqe-WyD1"), + "1490000001", + "13,19", + "")); + } + + Y_UNIT_TEST(GetExpirationTime) { + UNIT_ASSERT(!NTvmAuth::NInternal::TCanningKnife::GetExpirationTime("3:aadasdasdasdas")); + + UNIT_ASSERT(NTvmAuth::NInternal::TCanningKnife::GetExpirationTime(VALID_SERVICE_TICKET_1)); + UNIT_ASSERT_VALUES_EQUAL(TInstant::Seconds(std::numeric_limits<time_t>::max()), + *NTvmAuth::NInternal::TCanningKnife::GetExpirationTime(VALID_SERVICE_TICKET_1)); + + UNIT_ASSERT(NTvmAuth::NInternal::TCanningKnife::GetExpirationTime(EXPIRED_SERVICE_TICKET)); + UNIT_ASSERT_VALUES_EQUAL(TInstant::Seconds(0), + *NTvmAuth::NInternal::TCanningKnife::GetExpirationTime(EXPIRED_SERVICE_TICKET)); + } + + Y_UNIT_TEST(RemoveSignatureTest) { + UNIT_ASSERT_VALUES_EQUAL("1:serv:ASDkljbjhsdbfLJHABFJHBslfbsfjs:asdxcvbxcvniueliuweklsvds", + NUtils::RemoveTicketSignature("1:serv:ASDkljbjhsdbfLJHABFJHBslfbsfjs:asdxcvbxcvniueliuweklsvds")); + UNIT_ASSERT_VALUES_EQUAL("2:serv:ASDkljbjhsdbfLJHABFJHBslfbsfjs:asdxcvbxcvniueliuweklsvds", + NUtils::RemoveTicketSignature("2:serv:ASDkljbjhsdbfLJHABFJHBslfbsfjs:asdxcvbxcvniueliuweklsvds")); + UNIT_ASSERT_VALUES_EQUAL("4:serv:ASDkljbjhsdbfLJHABFJHBslfbsfjs:asdxcvbxcvniueliuweklsvds", + NUtils::RemoveTicketSignature("4:serv:ASDkljbjhsdbfLJHABFJHBslfbsfjs:asdxcvbxcvniueliuweklsvds")); + UNIT_ASSERT_VALUES_EQUAL("3.serv.ASDkljbjhsdbfLJHABFJHBslfbsfjs.asdxcvbxcvniueliuweklsvds", + NUtils::RemoveTicketSignature("3.serv.ASDkljbjhsdbfLJHABFJHBslfbsfjs.asdxcvbxcvniueliuweklsvds")); + UNIT_ASSERT_VALUES_EQUAL("3:serv:ASDkljbjhsdbfLJHABFJHBslfbsfjs:", + NUtils::RemoveTicketSignature("3:serv:ASDkljbjhsdbfLJHABFJHBslfbsfjs:asdxcvbxcvniueliuweklsvds")); + UNIT_ASSERT_VALUES_EQUAL("3:serv:ASDkljbjhsdbfLJHABFJHBslfbsfjs:", + NUtils::RemoveTicketSignature("3:serv:ASDkljbjhsdbfLJHABFJHBslfbsfjs:asdxcvbxcvniueliuweklsvds")); + UNIT_ASSERT_VALUES_EQUAL("3:serv:", + NUtils::RemoveTicketSignature("3:serv:ASDkljbjhsdbfLJHABFJHBslfbsfjs.asdxcvbxcvniueliuweklsvds")); + UNIT_ASSERT_VALUES_EQUAL("asdxcbvfgdsgfasdfxczvdsgfxcdvbcbvf", + NUtils::RemoveTicketSignature("asdxcbvfgdsgfasdfxczvdsgfxcdvbcbvf")); + } +} diff --git a/library/cpp/tvmauth/src/ut/version_ut.cpp b/library/cpp/tvmauth/src/ut/version_ut.cpp new file mode 100644 index 0000000000..eeb95d1cde --- /dev/null +++ b/library/cpp/tvmauth/src/ut/version_ut.cpp @@ -0,0 +1,18 @@ +#include <library/cpp/tvmauth/version.h> + +#include <library/cpp/testing/unittest/registar.h> + +#include <regex> + +using namespace NTvmAuth; + +Y_UNIT_TEST_SUITE(VersionTest) { + Y_UNIT_TEST(base64Test) { + const std::regex re(R"(^\d+\.\d+\.\d+$)"); + + for (size_t idx = 0; idx < 2; ++idx) { + TStringBuf ver = LibVersion(); + UNIT_ASSERT(std::regex_match(ver.begin(), ver.end(), re)); + } + } +} diff --git a/library/cpp/tvmauth/src/utils.cpp b/library/cpp/tvmauth/src/utils.cpp new file mode 100644 index 0000000000..f56fafd97b --- /dev/null +++ b/library/cpp/tvmauth/src/utils.cpp @@ -0,0 +1,162 @@ +#include "utils.h" + +#include "parser.h" + +#include <openssl/evp.h> +#include <openssl/hmac.h> +#include <openssl/md5.h> +#include <openssl/sha.h> + +#include <util/generic/maybe.h> +#include <util/generic/strbuf.h> + +#include <array> + +namespace { + constexpr const unsigned char b64_encode[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; + + constexpr std::array<unsigned char, 256> B64Init() { + std::array<unsigned char, 256> buf{}; + for (auto& i : buf) + i = 0xff; + + for (int i = 0; i < 64; ++i) + buf[b64_encode[i]] = i; + + return buf; + } + constexpr std::array<unsigned char, 256> b64_decode = B64Init(); +} + +namespace NTvmAuth::NUtils { + TString Bin2base64url(TStringBuf buf) { + if (!buf) { + return TString(); + } + + TString res; + res.resize(((buf.size() + 2) / 3) << 2, 0); + + const unsigned char* pB = (const unsigned char*)buf.data(); + const unsigned char* pE = (const unsigned char*)buf.data() + buf.size(); + unsigned char* p = (unsigned char*)res.data(); + for (; pB + 2 < pE; pB += 3) { + const unsigned char a = *pB; + *p++ = b64_encode[(a >> 2) & 0x3F]; + const unsigned char b = *(pB + 1); + *p++ = b64_encode[((a & 0x3) << 4) | ((b & 0xF0) >> 4)]; + const unsigned char c = *(pB + 2); + *p++ = b64_encode[((b & 0xF) << 2) | ((c & 0xC0) >> 6)]; + *p++ = b64_encode[c & 0x3F]; + } + + if (pB < pE) { + const unsigned char a = *pB; + *p++ = b64_encode[(a >> 2) & 0x3F]; + + if (pB == (pE - 1)) { + *p++ = b64_encode[((a & 0x3) << 4)]; + } else { + const unsigned char b = *(pB + 1); + *p++ = b64_encode[((a & 0x3) << 4) | + ((int)(b & 0xF0) >> 4)]; + *p++ = b64_encode[((b & 0xF) << 2)]; + } + } + + res.resize(p - (unsigned char*)res.data()); + return res; + } + + TString Base64url2bin(TStringBuf buf) { + const unsigned char* bufin = (const unsigned char*)buf.data(); + if (!buf || b64_decode[*bufin] > 63) { + return TString(); + } + const unsigned char* bufend = (const unsigned char*)buf.data() + buf.size(); + while (++bufin < bufend && b64_decode[*bufin] < 64) + ; + int nprbytes = (bufin - (const unsigned char*)buf.data()); + int nbytesdecoded = ((nprbytes + 3) / 4) * 3; + + if (nprbytes < static_cast<int>(buf.size())) { + int left = buf.size() - nprbytes; + while (left--) { + if (*(bufin++) != '=') + return TString(); + } + } + + TString res; + res.resize(nbytesdecoded); + + unsigned char* bufout = (unsigned char*)res.data(); + bufin = (const unsigned char*)buf.data(); + + while (nprbytes > 4) { + unsigned char a = b64_decode[*bufin]; + unsigned char b = b64_decode[bufin[1]]; + *(bufout++) = (unsigned char)(a << 2 | b >> 4); + unsigned char c = b64_decode[bufin[2]]; + *(bufout++) = (unsigned char)(b << 4 | c >> 2); + unsigned char d = b64_decode[bufin[3]]; + *(bufout++) = (unsigned char)(c << 6 | d); + bufin += 4; + nprbytes -= 4; + } + + if (nprbytes == 1) { + return {}; // Impossible + } + if (nprbytes > 1) { + *(bufout++) = (unsigned char)(b64_decode[*bufin] << 2 | b64_decode[bufin[1]] >> 4); + } + if (nprbytes > 2) { + *(bufout++) = (unsigned char)(b64_decode[bufin[1]] << 4 | b64_decode[bufin[2]] >> 2); + } + if (nprbytes > 3) { + *(bufout++) = (unsigned char)(b64_decode[bufin[2]] << 6 | b64_decode[bufin[3]]); + } + + int diff = (4 - nprbytes) & 3; + if (diff) { + nbytesdecoded -= (4 - nprbytes) & 3; + res.resize(nbytesdecoded); + } + + return res; + } + + TString SignCgiParamsForTvm(TStringBuf secret, TStringBuf ts, TStringBuf dstTvmId, TStringBuf scopes) { + TString data; + data.reserve(ts.size() + dstTvmId.size() + scopes.size() + 3); + const char DELIM = '|'; + data.append(ts).push_back(DELIM); + data.append(dstTvmId).push_back(DELIM); + data.append(scopes).push_back(DELIM); + + TString value(EVP_MAX_MD_SIZE, 0); + unsigned macLen = 0; + + if (!::HMAC(EVP_sha256(), secret.data(), secret.size(), (unsigned char*)data.data(), data.size(), + (unsigned char*)value.data(), &macLen)) + { + return {}; + } + + if (macLen != EVP_MAX_MD_SIZE) { + value.resize(macLen); + } + return Bin2base64url(value); + } +} + +namespace NTvmAuth::NInternal { + TMaybe<TInstant> TCanningKnife::GetExpirationTime(TStringBuf ticket) { + const TParserTickets::TRes res = TParserTickets::ParseV3(ticket, {}, TParserTickets::ServiceFlag()); + + return res.Status == ETicketStatus::MissingKey || res.Status == ETicketStatus::Expired + ? TInstant::Seconds(res.Ticket.expirationtime()) + : TMaybe<TInstant>(); + } +} diff --git a/library/cpp/tvmauth/src/utils.h b/library/cpp/tvmauth/src/utils.h new file mode 100644 index 0000000000..e5847ac89f --- /dev/null +++ b/library/cpp/tvmauth/src/utils.h @@ -0,0 +1,30 @@ +#pragma once + +#include <library/cpp/tvmauth/checked_service_ticket.h> +#include <library/cpp/tvmauth/checked_user_ticket.h> +#include <library/cpp/tvmauth/ticket_status.h> + +#include <util/datetime/base.h> +#include <util/generic/fwd.h> + +namespace NTvmAuth::NUtils { + TString Bin2base64url(TStringBuf buf); + TString Base64url2bin(TStringBuf buf); + + TString SignCgiParamsForTvm(TStringBuf secret, TStringBuf ts, TStringBuf dstTvmId, TStringBuf scopes); +} + +namespace NTvmAuth::NInternal { + class TCanningKnife { + public: + static TCheckedServiceTicket::TImpl* GetS(TCheckedServiceTicket& t) { + return t.Impl_.Release(); + } + + static TCheckedUserTicket::TImpl* GetU(TCheckedUserTicket& t) { + return t.Impl_.Release(); + } + + static TMaybe<TInstant> GetExpirationTime(TStringBuf ticket); + }; +} diff --git a/library/cpp/tvmauth/src/version b/library/cpp/tvmauth/src/version new file mode 100644 index 0000000000..4f5e69734c --- /dev/null +++ b/library/cpp/tvmauth/src/version @@ -0,0 +1 @@ +3.4.5 diff --git a/library/cpp/tvmauth/src/version.cpp b/library/cpp/tvmauth/src/version.cpp new file mode 100644 index 0000000000..6b389213d0 --- /dev/null +++ b/library/cpp/tvmauth/src/version.cpp @@ -0,0 +1,26 @@ +#include <library/cpp/resource/resource.h> + +#include <util/string/strip.h> + +namespace { + class TBuiltinVersion { + public: + TBuiltinVersion() { + Version_ = NResource::Find("/builtin/version"); + StripInPlace(Version_); + } + + TStringBuf Get() const { + return Version_; + } + + private: + TString Version_; + }; +} + +namespace NTvmAuth { + TStringBuf LibVersion() { + return Singleton<TBuiltinVersion>()->Get(); + } +} diff --git a/library/cpp/tvmauth/ticket_status.h b/library/cpp/tvmauth/ticket_status.h new file mode 100644 index 0000000000..532d4de56e --- /dev/null +++ b/library/cpp/tvmauth/ticket_status.h @@ -0,0 +1,23 @@ +#pragma once + +#include <util/generic/strbuf.h> + +namespace NTvmAuth { + /*! + * Status mean result of ticket check + */ + enum class ETicketStatus { + Ok, + Expired, + InvalidBlackboxEnv, + InvalidDst, + InvalidTicketType, + Malformed, + MissingKey, + SignBroken, + UnsupportedVersion, + NoRoles, + }; + + TStringBuf StatusToString(ETicketStatus st); +} diff --git a/library/cpp/tvmauth/type.h b/library/cpp/tvmauth/type.h new file mode 100644 index 0000000000..7f4ce2b700 --- /dev/null +++ b/library/cpp/tvmauth/type.h @@ -0,0 +1,11 @@ +#pragma once + +#include <library/cpp/containers/stack_vector/stack_vec.h> + +namespace NTvmAuth { + using TScopes = TSmallVec<TStringBuf>; + using TTvmId = ui32; + using TUid = ui64; + using TUids = TSmallVec<TUid>; + using TAlias = TString; +} diff --git a/library/cpp/tvmauth/unittest.h b/library/cpp/tvmauth/unittest.h new file mode 100644 index 0000000000..efa651befa --- /dev/null +++ b/library/cpp/tvmauth/unittest.h @@ -0,0 +1,20 @@ +#pragma once + +#include "checked_service_ticket.h" +#include "checked_user_ticket.h" + +#include <util/generic/maybe.h> + +namespace NTvmAuth::NUnittest { + static const TString TVMKNIFE_PUBLIC_KEYS = "1:CpgCCpMCCAEQABqIAjCCAQQCggEAcLEXeH67FQESFUn4_7wnX7wN0PUrBoUsm3QQ4W5vC-qz6sXaEjSwnTV8w1o-z6X9KPLlhzMQvuS38NCNfK4uvJ4Zvfp3YsXJ25-rYtbnrYJHNvHohD-kPCCw_yZpMp21JdWigzQGuV7CtrxUhF-NNrsnUaJrE5-OpEWNt4X6nCItKIYeVcSK6XJUbEWbrNCRbvkSc4ak2ymFeMuHYJVjxh4eQbk7_ZPzodP0WvF6eUYrYeb42imVEOR8ofVLQWE5DVnb1z_TqZm4i1XkS7jMwZuBxBRw8DGdYei0lT_sAf7KST2jC0590NySB3vsBgWEVs1OdUUWA6r-Dvx9dsOQtSCVkQYQAAqZAgqUAggCEAAaiQIwggEFAoIBAQDhEBM5-6YsPWfogKtbluJoCX1WV2KdzOaQ0-OlRbBzeCzw-eQKu12c8WakHBbeCMd1I1TU64SDkDorWjXGIa_2xT6N3zzNAE50roTbPCcmeQrps26woTYfYIuqDdoxYKZNr0lvNLLW47vBr7EKqo1S4KSj7aXK_XYeEvUgIgf3nVIcNrio7VTnFmGGVQCepaL1Hi1gN4yIXjVZ06PBPZ-DxSRu6xOGbFrfKMJeMPs7KOyE-26Q3xOXdTIa1X-zYIucTd_bxUCL4BVbwW2AvbbFsaG7ISmVdGu0XUTmhXs1KrEfUVLRJhE4Dx99hAZXm1_HlYMUeJcMQ_oHOhV94ENFIJaRBhACCpYBCpEBCAMQABqGATCBgwKBgF9t2YJGAJkRRFq6fWhi3m1TFW1UOE0f6ZrfYhHAkpqGlKlh0QVfeTNPpeJhi75xXzCe6oReRUm-0DbqDNhTShC7uGUv1INYnRBQWH6E-5Fc5XrbDFSuGQw2EYjNfHy_HefHJXxQKAqPvxBDKMKkHgV58WtM6rC8jRi9sdX_ig2NIJeRBhABCpYBCpEBCAQQABqGATCBgwKBgGB4d6eLGUBv-Q6EPLehC4S-yuE2HB-_rJ7WkeYwyp-xIPolPrd-PQme2utHB4ZgpXHIu_OFksDe_0bPgZniNRSVRbl7W49DgS5Ya3kMfrYB4DnF5Fta5tn1oV6EwxYD4JONpFTenOJALPGTPawxXEfon_peiHOSBuQMu3_Vn-l1IJiRBhADCpcBCpIBCAUQABqHATCBhAKBgQCTJMKIfmfeZpaI7Q9rnsc29gdWawK7TnpVKRHws1iY7EUlYROeVcMdAwEqVM6f8BVCKLGgzQ7Gar_uuxfUGKwqEQzoppDraw4F75J464-7D5f6_oJQuGIBHZxqbMONtLjBCXRUhQW5szBLmTQ_R3qaJb5vf-h0APZfkYhq1cTttSCZkQYQBAqWAQqRAQgLEAAahgEwgYMCgYBvvGVH_M2H8qxxv94yaDYUTWbRnJ1uiIYc59KIQlfFimMPhSS7x2tqUa2-hI55JiII0Xym6GNkwLhyc1xtWChpVuIdSnbvttbrt4weDMLHqTwNOF6qAsVKGKT1Yh8yf-qb-DSmicgvFc74mBQm_6gAY1iQsf33YX8578ClhKBWHSCVkQYQAAqXAQqSAQgMEAAahwEwgYQCgYEAkuzFcd5TJu7lYWYe2hQLFfUWIIj91BvQQLa_Thln4YtGCO8gG1KJqJm-YlmJOWQG0B7H_5RVhxUxV9KpmFnsDVkzUFKOsCBaYGXc12xPVioawUlAwp5qp3QQtZyx_se97YIoLzuLr46UkLcLnkIrp-Jo46QzYi_QHq45WTm8MQ0glpEGEAIKlwEKkgEIDRAAGocBMIGEAoGBAIUzbxOknXf_rNt17_ir8JlWvrtnCWsQd1MAnl5mgArvavDtKeBYHzi5_Ak7DHlLzuA6YE8W175FxLFKpN2hkz-l-M7ltUSd8N1BvJRhK4t6WffWfC_1wPyoAbeSN2Yb1jygtZJQ8wGoXHcJQUXiMit3eFNyylwsJFj1gzAR4JCdIJeRBhABCpYBCpEBCA4QABqGATCBgwKBgFMcbEpl9ukVR6AO_R6sMyiU11I8b8MBSUCEC15iKsrVO8v_m47_TRRjWPYtQ9eZ7o1ocNJHaGUU7qqInFqtFaVnIceP6NmCsXhjs3MLrWPS8IRAy4Zf4FKmGOx3N9O2vemjUygZ9vUiSkULdVrecinRaT8JQ5RG4bUMY04XGIwFIJiRBhADCpYBCpEBCA8QABqGATCBgwKBgGpCkW-NR3li8GlRvqpq2YZGSIgm_PTyDI2Zwfw69grsBmPpVFW48Vw7xoMN35zcrojEpialB_uQzlpLYOvsMl634CRIuj-n1QE3-gaZTTTE8mg-AR4mcxnTKThPnRQpbuOlYAnriwiasWiQEMbGjq_HmWioYYxFo9USlklQn4-9IJmRBhAEEpUBCpIBCAYQABqHATCBhAKBgQCoZkFGm9oLTqjeXZAq6j5S6i7K20V0lNdBBLqfmFBIRuTkYxhs4vUYnWjZrKRAd5bp6_py0csmFmpl_5Yh0b-2pdo_E5PNP7LGRzKyKSiFddyykKKzVOazH8YYldDAfE8Z5HoS9e48an5JsPg0jr-TPu34DnJq3yv2a6dqiKL9zSCakQYSlQEKkgEIEBAAGocBMIGEAoGBALhrihbf3EpjDQS2sCQHazoFgN0nBbE9eesnnFTfzQELXb2gnJU9enmV_aDqaHKjgtLIPpCgn40lHrn5k6mvH5OdedyI6cCzE-N-GFp3nAq0NDJyMe0fhtIRD__CbT0ulcvkeow65ubXWfw6dBC2gR_34rdMe_L_TGRLMWjDULbNIJqRBg"; + + TCheckedServiceTicket CreateServiceTicket(ETicketStatus status, + TTvmId src, + TMaybe<TUid> issuerUid = TMaybe<TUid>()); + + TCheckedUserTicket CreateUserTicket(ETicketStatus status, + TUid defaultUid, + const TScopes& scopes, + const TUids& uids = TUids(), + EBlackboxEnv env = EBlackboxEnv::Test); +} diff --git a/library/cpp/tvmauth/utils.cpp b/library/cpp/tvmauth/utils.cpp new file mode 100644 index 0000000000..a06cd6f5ba --- /dev/null +++ b/library/cpp/tvmauth/utils.cpp @@ -0,0 +1,18 @@ +#include "utils.h" + +namespace NTvmAuth::NUtils { + TStringBuf RemoveTicketSignature(TStringBuf ticketBody) { + if (ticketBody.size() < 2 || + ticketBody[0] != '3' || + ticketBody[1] != ':') { + return ticketBody; + } + + size_t pos = ticketBody.rfind(':'); + if (pos == TStringBuf::npos) { // impossible + return ticketBody; + } + + return ticketBody.substr(0, pos + 1); + } +} diff --git a/library/cpp/tvmauth/utils.h b/library/cpp/tvmauth/utils.h new file mode 100644 index 0000000000..ad8950cab5 --- /dev/null +++ b/library/cpp/tvmauth/utils.h @@ -0,0 +1,12 @@ +#pragma once + +#include <util/generic/strbuf.h> + +namespace NTvmAuth::NUtils { + /*! + * Remove signature from ticket string - rest part can be parsed later with `tvmknife parse_ticket ...` + * @param ticketBody Raw ticket body + * @return safe for logging part of ticket + */ + TStringBuf RemoveTicketSignature(TStringBuf ticketBody); +} diff --git a/library/cpp/tvmauth/version.h b/library/cpp/tvmauth/version.h new file mode 100644 index 0000000000..48ec279829 --- /dev/null +++ b/library/cpp/tvmauth/version.h @@ -0,0 +1,7 @@ +#pragma once + +#include <util/generic/strbuf.h> + +namespace NTvmAuth { + TStringBuf LibVersion(); +} |