aboutsummaryrefslogtreecommitdiffstats
path: root/library/cpp/tvmauth
diff options
context:
space:
mode:
authorqrort <qrort@yandex-team.com>2022-11-30 23:47:12 +0300
committerqrort <qrort@yandex-team.com>2022-11-30 23:47:12 +0300
commit22f8ae0e3f5d68b92aecccdf96c1d841a0334311 (patch)
treebffa27765faf54126ad44bcafa89fadecb7a73d7 /library/cpp/tvmauth
parent332b99e2173f0425444abb759eebcb2fafaa9209 (diff)
downloadydb-22f8ae0e3f5d68b92aecccdf96c1d841a0334311.tar.gz
validate canons without yatest_common
Diffstat (limited to 'library/cpp/tvmauth')
-rw-r--r--library/cpp/tvmauth/README.md2
-rw-r--r--library/cpp/tvmauth/checked_service_ticket.h76
-rw-r--r--library/cpp/tvmauth/checked_user_ticket.h91
-rw-r--r--library/cpp/tvmauth/client/README.md84
-rw-r--r--library/cpp/tvmauth/client/client_status.cpp6
-rw-r--r--library/cpp/tvmauth/client/client_status.h82
-rw-r--r--library/cpp/tvmauth/client/examples/create_with_tvmapi/create.cpp102
-rw-r--r--library/cpp/tvmauth/client/examples/create_with_tvmtool/create.cpp34
-rw-r--r--library/cpp/tvmauth/client/examples/service_using_tvmtool_client/service.cpp84
-rw-r--r--library/cpp/tvmauth/client/examples/service_using_tvmtool_client/service.h35
-rw-r--r--library/cpp/tvmauth/client/exception.h26
-rw-r--r--library/cpp/tvmauth/client/facade.cpp155
-rw-r--r--library/cpp/tvmauth/client/facade.h124
-rw-r--r--library/cpp/tvmauth/client/logger.cpp12
-rw-r--r--library/cpp/tvmauth/client/logger.h59
-rw-r--r--library/cpp/tvmauth/client/misc/api/dynamic_dst/tvm_client.cpp166
-rw-r--r--library/cpp/tvmauth/client/misc/api/dynamic_dst/tvm_client.h60
-rw-r--r--library/cpp/tvmauth/client/misc/api/dynamic_dst/ut/tvm_client_ut.cpp760
-rw-r--r--library/cpp/tvmauth/client/misc/api/retry_settings.h33
-rw-r--r--library/cpp/tvmauth/client/misc/api/roles_fetcher.cpp164
-rw-r--r--library/cpp/tvmauth/client/misc/api/roles_fetcher.h63
-rw-r--r--library/cpp/tvmauth/client/misc/api/settings.cpp89
-rw-r--r--library/cpp/tvmauth/client/misc/api/settings.h236
-rw-r--r--library/cpp/tvmauth/client/misc/api/threaded_updater.cpp1049
-rw-r--r--library/cpp/tvmauth/client/misc/api/threaded_updater.h153
-rw-r--r--library/cpp/tvmauth/client/misc/async_updater.cpp152
-rw-r--r--library/cpp/tvmauth/client/misc/async_updater.h114
-rw-r--r--library/cpp/tvmauth/client/misc/checker.h38
-rw-r--r--library/cpp/tvmauth/client/misc/default_uid_checker.h31
-rw-r--r--library/cpp/tvmauth/client/misc/disk_cache.cpp162
-rw-r--r--library/cpp/tvmauth/client/misc/disk_cache.h50
-rw-r--r--library/cpp/tvmauth/client/misc/exponential_backoff.h94
-rw-r--r--library/cpp/tvmauth/client/misc/fetch_result.h13
-rw-r--r--library/cpp/tvmauth/client/misc/getter.h49
-rw-r--r--library/cpp/tvmauth/client/misc/last_error.cpp115
-rw-r--r--library/cpp/tvmauth/client/misc/last_error.h51
-rw-r--r--library/cpp/tvmauth/client/misc/proc_info.cpp53
-rw-r--r--library/cpp/tvmauth/client/misc/proc_info.h18
-rw-r--r--library/cpp/tvmauth/client/misc/retry_settings/v1/settings.proto21
-rw-r--r--library/cpp/tvmauth/client/misc/roles/decoder.cpp93
-rw-r--r--library/cpp/tvmauth/client/misc/roles/decoder.h32
-rw-r--r--library/cpp/tvmauth/client/misc/roles/entities_index.cpp114
-rw-r--r--library/cpp/tvmauth/client/misc/roles/entities_index.h107
-rw-r--r--library/cpp/tvmauth/client/misc/roles/parser.cpp149
-rw-r--r--library/cpp/tvmauth/client/misc/roles/parser.h36
-rw-r--r--library/cpp/tvmauth/client/misc/roles/roles.cpp101
-rw-r--r--library/cpp/tvmauth/client/misc/roles/roles.h186
-rw-r--r--library/cpp/tvmauth/client/misc/roles/types.h70
-rw-r--r--library/cpp/tvmauth/client/misc/service_tickets.h86
-rw-r--r--library/cpp/tvmauth/client/misc/settings.h13
-rw-r--r--library/cpp/tvmauth/client/misc/src_checker.h31
-rw-r--r--library/cpp/tvmauth/client/misc/threaded_updater.cpp111
-rw-r--r--library/cpp/tvmauth/client/misc/threaded_updater.h76
-rw-r--r--library/cpp/tvmauth/client/misc/tool/meta_info.cpp208
-rw-r--r--library/cpp/tvmauth/client/misc/tool/meta_info.h69
-rw-r--r--library/cpp/tvmauth/client/misc/tool/roles_fetcher.cpp81
-rw-r--r--library/cpp/tvmauth/client/misc/tool/roles_fetcher.h49
-rw-r--r--library/cpp/tvmauth/client/misc/tool/settings.cpp37
-rw-r--r--library/cpp/tvmauth/client/misc/tool/settings.h169
-rw-r--r--library/cpp/tvmauth/client/misc/tool/threaded_updater.cpp370
-rw-r--r--library/cpp/tvmauth/client/misc/tool/threaded_updater.h58
-rw-r--r--library/cpp/tvmauth/client/misc/utils.cpp46
-rw-r--r--library/cpp/tvmauth/client/misc/utils.h95
-rw-r--r--library/cpp/tvmauth/client/mocked_updater.cpp60
-rw-r--r--library/cpp/tvmauth/client/mocked_updater.h44
-rw-r--r--library/cpp/tvmauth/client/ut/async_updater_ut.cpp165
-rw-r--r--library/cpp/tvmauth/client/ut/checker_ut.cpp189
-rw-r--r--library/cpp/tvmauth/client/ut/client_status_ut.cpp18
-rw-r--r--library/cpp/tvmauth/client/ut/common.h240
-rw-r--r--library/cpp/tvmauth/client/ut/default_uid_checker_ut.cpp47
-rw-r--r--library/cpp/tvmauth/client/ut/disk_cache_ut.cpp204
-rw-r--r--library/cpp/tvmauth/client/ut/exponential_backoff_ut.cpp44
-rw-r--r--library/cpp/tvmauth/client/ut/facade_ut.cpp205
-rw-r--r--library/cpp/tvmauth/client/ut/files/ok.cachebin0 -> 113 bytes
-rw-r--r--library/cpp/tvmauth/client/ut/files/public_keysbin0 -> 2840 bytes
-rw-r--r--library/cpp/tvmauth/client/ut/files/rolesbin0 -> 295 bytes
-rw-r--r--library/cpp/tvmauth/client/ut/files/service_ticketsbin0 -> 250 bytes
-rw-r--r--library/cpp/tvmauth/client/ut/last_error_ut.cpp56
-rw-r--r--library/cpp/tvmauth/client/ut/logger_ut.cpp43
-rw-r--r--library/cpp/tvmauth/client/ut/roles/decoder_ut.cpp163
-rw-r--r--library/cpp/tvmauth/client/ut/roles/entities_index_ut.cpp358
-rw-r--r--library/cpp/tvmauth/client/ut/roles/parser_ut.cpp161
-rw-r--r--library/cpp/tvmauth/client/ut/roles/roles_ut.cpp419
-rw-r--r--library/cpp/tvmauth/client/ut/roles/tvmapi_roles_fetcher_ut.cpp197
-rw-r--r--library/cpp/tvmauth/client/ut/roles/tvmtool_roles_fetcher_ut.cpp103
-rw-r--r--library/cpp/tvmauth/client/ut/settings_ut.cpp158
-rw-r--r--library/cpp/tvmauth/client/ut/src_checker_ut.cpp41
-rw-r--r--library/cpp/tvmauth/client/ut/tvmapi_updater_ut.cpp1326
-rw-r--r--library/cpp/tvmauth/client/ut/tvmtool_updater_ut.cpp756
-rw-r--r--library/cpp/tvmauth/client/ut/utils_ut.cpp88
-rw-r--r--library/cpp/tvmauth/deprecated/service_context.cpp37
-rw-r--r--library/cpp/tvmauth/deprecated/service_context.h72
-rw-r--r--library/cpp/tvmauth/deprecated/user_context.cpp20
-rw-r--r--library/cpp/tvmauth/deprecated/user_context.h30
-rw-r--r--library/cpp/tvmauth/exception.h20
-rw-r--r--library/cpp/tvmauth/src/parser.cpp97
-rw-r--r--library/cpp/tvmauth/src/parser.h51
-rw-r--r--library/cpp/tvmauth/src/protos/ticket2.proto31
-rw-r--r--library/cpp/tvmauth/src/protos/tvm_keys.proto36
-rw-r--r--library/cpp/tvmauth/src/rw/keys.cpp138
-rw-r--r--library/cpp/tvmauth/src/rw/keys.h65
-rw-r--r--library/cpp/tvmauth/src/rw/rw.h86
-rw-r--r--library/cpp/tvmauth/src/rw/rw_asn1.c81
-rw-r--r--library/cpp/tvmauth/src/rw/rw_key.c135
-rw-r--r--library/cpp/tvmauth/src/rw/rw_lib.c77
-rw-r--r--library/cpp/tvmauth/src/rw/rw_ossl.c481
-rw-r--r--library/cpp/tvmauth/src/rw/rw_pss.c328
-rw-r--r--library/cpp/tvmauth/src/rw/rw_pss_sign.c211
-rw-r--r--library/cpp/tvmauth/src/rw/rw_sign.c46
-rw-r--r--library/cpp/tvmauth/src/rw/ut/rw_ut.cpp200
-rw-r--r--library/cpp/tvmauth/src/rw/ut_large/gen/main.cpp32
-rw-r--r--library/cpp/tvmauth/src/rw/ut_large/test.py35
-rw-r--r--library/cpp/tvmauth/src/service_impl.cpp204
-rw-r--r--library/cpp/tvmauth/src/service_impl.h77
-rw-r--r--library/cpp/tvmauth/src/service_ticket.cpp46
-rw-r--r--library/cpp/tvmauth/src/status.cpp32
-rw-r--r--library/cpp/tvmauth/src/unittest.cpp14
-rw-r--r--library/cpp/tvmauth/src/user_impl.cpp241
-rw-r--r--library/cpp/tvmauth/src/user_impl.h72
-rw-r--r--library/cpp/tvmauth/src/user_ticket.cpp56
-rw-r--r--library/cpp/tvmauth/src/ut/parser_ut.cpp143
-rw-r--r--library/cpp/tvmauth/src/ut/public_ut.cpp290
-rw-r--r--library/cpp/tvmauth/src/ut/service_ut.cpp156
-rw-r--r--library/cpp/tvmauth/src/ut/user_ut.cpp216
-rw-r--r--library/cpp/tvmauth/src/ut/utils_ut.cpp95
-rw-r--r--library/cpp/tvmauth/src/ut/version_ut.cpp18
-rw-r--r--library/cpp/tvmauth/src/utils.cpp162
-rw-r--r--library/cpp/tvmauth/src/utils.h30
-rw-r--r--library/cpp/tvmauth/src/version1
-rw-r--r--library/cpp/tvmauth/src/version.cpp26
-rw-r--r--library/cpp/tvmauth/ticket_status.h23
-rw-r--r--library/cpp/tvmauth/type.h11
-rw-r--r--library/cpp/tvmauth/unittest.h20
-rw-r--r--library/cpp/tvmauth/utils.cpp18
-rw-r--r--library/cpp/tvmauth/utils.h12
-rw-r--r--library/cpp/tvmauth/version.h7
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 = [&currentResp, &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
new file mode 100644
index 0000000000..768d4953d1
--- /dev/null
+++ b/library/cpp/tvmauth/client/ut/files/ok.cache
Binary files differ
diff --git a/library/cpp/tvmauth/client/ut/files/public_keys b/library/cpp/tvmauth/client/ut/files/public_keys
new file mode 100644
index 0000000000..fa683d18f3
--- /dev/null
+++ b/library/cpp/tvmauth/client/ut/files/public_keys
Binary files differ
diff --git a/library/cpp/tvmauth/client/ut/files/roles b/library/cpp/tvmauth/client/ut/files/roles
new file mode 100644
index 0000000000..36864ae50a
--- /dev/null
+++ b/library/cpp/tvmauth/client/ut/files/roles
Binary files differ
diff --git a/library/cpp/tvmauth/client/ut/files/service_tickets b/library/cpp/tvmauth/client/ut/files/service_tickets
new file mode 100644
index 0000000000..7a6985a34d
--- /dev/null
+++ b/library/cpp/tvmauth/client/ut/files/service_tickets
Binary files differ
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();
+}