aboutsummaryrefslogtreecommitdiffstats
path: root/library/cpp/tvmauth/client/misc/api/roles_fetcher.cpp
blob: 8f4b359e8cf0e43a66b5923bcdb94250e9bcdf5c (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
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;
    }
}