#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;
}
}