Skip to content

Commit e6a5701

Browse files
authored
[oidc proxy] Using TContext structure (#8982)
1 parent 4583995 commit e6a5701

14 files changed

+344
-192
lines changed

ydb/mvp/oidc_proxy/context.cpp

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
#include <util/generic/string.h>
2+
#include <util/random/random.h>
3+
#include <util/string/builder.h>
4+
#include <library/cpp/string_utils/base64/base64.h>
5+
#include <ydb/library/actors/http/http.h>
6+
#include "openid_connect.h"
7+
#include "context.h"
8+
9+
namespace NMVP {
10+
namespace NOIDC {
11+
12+
TContext::TContext(const TString& state, const TString& requestedAddress, bool isAjaxRequest)
13+
: State(state)
14+
, AjaxRequest(isAjaxRequest)
15+
, RequestedAddress(requestedAddress)
16+
{}
17+
18+
TContext::TContext(const NHttp::THttpIncomingRequestPtr& request)
19+
: State(GenerateState())
20+
, AjaxRequest(DetectAjaxRequest(request))
21+
, RequestedAddress(GetRequestedUrl(request, AjaxRequest))
22+
{}
23+
24+
TString TContext::GetState() const {
25+
return State;
26+
}
27+
28+
bool TContext::IsAjaxRequest() const {
29+
return AjaxRequest;
30+
}
31+
32+
TString TContext::GetRequestedAddress() const {
33+
return RequestedAddress;
34+
}
35+
36+
TString TContext::CreateYdbOidcCookie(const TString& secret) const {
37+
static constexpr size_t COOKIE_MAX_AGE_SEC = 420;
38+
return TStringBuilder() << CreateNameYdbOidcCookie(secret, State) << "="
39+
<< GenerateCookie(secret) << ";"
40+
" Path=" << GetAuthCallbackUrl() << ";"
41+
" Max-Age=" << COOKIE_MAX_AGE_SEC << ";"
42+
" SameSite=None; Secure";
43+
}
44+
45+
TString TContext::GenerateCookie(const TString& secret) const {
46+
const TDuration StateLifeTime = TDuration::Minutes(10);
47+
TInstant expirationTime = TInstant::Now() + StateLifeTime;
48+
TStringBuilder stateStruct;
49+
stateStruct << "{\"state\":\"" << State
50+
<< "\",\"requested_address\":\"" << RequestedAddress
51+
<< "\",\"expiration_time\":" << ToString(expirationTime.TimeT())
52+
<< ",\"ajax_request\":" << (AjaxRequest ? "true" : "false") << "}";
53+
TString digest = HmacSHA256(secret, stateStruct);
54+
TString cookieStruct {"{\"state_struct\":\"" + Base64Encode(stateStruct) + "\",\"digest\":\"" + Base64Encode(digest) + "\"}"};
55+
return Base64Encode(cookieStruct);
56+
}
57+
58+
TString TContext::GenerateState() {
59+
TStringBuilder sb;
60+
static constexpr size_t CHAR_NUMBER = 15;
61+
for (size_t i{0}; i < CHAR_NUMBER; i++) {
62+
sb << RandomNumber<char>();
63+
}
64+
return Base64EncodeUrlNoPadding(sb);
65+
}
66+
67+
bool TContext::DetectAjaxRequest(const NHttp::THttpIncomingRequestPtr& request) {
68+
static const THashMap<TStringBuf, TStringBuf> expectedHeaders {
69+
{"Accept", "application/json"}
70+
};
71+
NHttp::THeaders headers(request->Headers);
72+
for (const auto& el : expectedHeaders) {
73+
TStringBuf headerValue = headers.Get(el.first);
74+
if (!headerValue || headerValue.find(el.second) == TStringBuf::npos) {
75+
return false;
76+
}
77+
}
78+
return true;
79+
}
80+
81+
TStringBuf TContext::GetRequestedUrl(const NHttp::THttpIncomingRequestPtr& request, bool isAjaxRequest) {
82+
NHttp::THeaders headers(request->Headers);
83+
TStringBuf requestedUrl = headers.Get("Referer");
84+
if (!isAjaxRequest || requestedUrl.empty()) {
85+
return request->URL;
86+
}
87+
return requestedUrl;
88+
}
89+
90+
} // NOIDC
91+
} // NMVP

ydb/mvp/oidc_proxy/context.h

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#pragma once
2+
3+
#include <util/generic/string.h>
4+
#include <util/generic/ptr.h>
5+
6+
namespace NHttp {
7+
8+
class THttpIncomingRequest;
9+
using THttpIncomingRequestPtr = TIntrusivePtr<THttpIncomingRequest>;
10+
11+
}
12+
13+
namespace NMVP {
14+
namespace NOIDC {
15+
16+
class TContext {
17+
private:
18+
TString State;
19+
bool AjaxRequest = false;
20+
TString RequestedAddress;
21+
22+
public:
23+
TContext(const TString& state = "", const TString& requestedAddress = "", bool isAjaxRequest = false);
24+
TContext(const NHttp::THttpIncomingRequestPtr& request);
25+
26+
TString GetState() const;
27+
bool IsAjaxRequest() const;
28+
TString GetRequestedAddress() const;
29+
30+
TString CreateYdbOidcCookie(const TString& secret) const;
31+
32+
private:
33+
static TString GenerateState();
34+
static bool DetectAjaxRequest(const NHttp::THttpIncomingRequestPtr& request);
35+
static TStringBuf GetRequestedUrl(const NHttp::THttpIncomingRequestPtr& request, bool isAjaxRequest);
36+
37+
TString GenerateCookie(const TString& secret) const;
38+
};
39+
40+
} // NOIDC
41+
} // NMVP

ydb/mvp/oidc_proxy/oidc_protected_page.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ void THandlerSessionServiceCheck::Bootstrap(const NActors::TActorContext& ctx) {
2626
return;
2727
}
2828
NHttp::THeaders headers(Request->Headers);
29-
IsAjaxRequest = DetectAjaxRequest(headers);
3029
TStringBuf authHeader = headers.Get(AUTH_HEADER_NAME);
3130
if (Request->Method == "OPTIONS" || IsAuthorizedRequest(authHeader)) {
3231
ForwardUserRequest(TString(authHeader), ctx);

ydb/mvp/oidc_proxy/oidc_protected_page.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ class THandlerSessionServiceCheck : public NActors::TActorBootstrapped<THandlerS
2121
const TOpenIdConnectSettings Settings;
2222
TString ProtectedPageUrl;
2323
TString RequestedPageScheme;
24-
bool IsAjaxRequest = false;
2524

2625
const static inline TStringBuf IAM_TOKEN_SCHEME = "Bearer ";
2726
const static inline TStringBuf IAM_TOKEN_SCHEME_LOWER = "bearer ";

ydb/mvp/oidc_proxy/oidc_protected_page_nebius.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include <ydb/mvp/core/mvp_tokens.h>
66
#include <ydb/mvp/core/mvp_log.h>
77
#include "openid_connect.h"
8+
#include "context.h"
89
#include "oidc_protected_page_nebius.h"
910

1011
namespace NMVP {
@@ -99,7 +100,8 @@ void THandlerSessionServiceCheckNebius::ExchangeSessionToken(const TString sessi
99100

100101
void THandlerSessionServiceCheckNebius::RequestAuthorizationCode(const NActors::TActorContext& ctx) {
101102
LOG_DEBUG_S(ctx, EService::MVP, "Request authorization code");
102-
NHttp::THttpOutgoingResponsePtr httpResponse = GetHttpOutgoingResponsePtr(Request, Settings, IsAjaxRequest);
103+
TContext context(Request);
104+
NHttp::THttpOutgoingResponsePtr httpResponse = GetHttpOutgoingResponsePtr(Request, Settings, context);
103105
ctx.Send(Sender, new NHttp::TEvHttpProxy::TEvHttpOutgoingResponse(httpResponse));
104106
Die(ctx);
105107
}

ydb/mvp/oidc_proxy/oidc_protected_page_yandex.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include <ydb/mvp/core/mvp_tokens.h>
33
#include <ydb/mvp/core/appdata.h>
44
#include <ydb/mvp/core/mvp_log.h>
5+
#include "context.h"
56
#include "oidc_protected_page_yandex.h"
67

78
namespace NMVP {
@@ -31,7 +32,8 @@ void THandlerSessionServiceCheckYandex::Handle(TEvPrivate::TEvErrorResponse::TPt
3132
LOG_DEBUG_S(ctx, EService::MVP, "SessionService.Check(): " << event->Get()->Status);
3233
NHttp::THttpOutgoingResponsePtr httpResponse;
3334
if (event->Get()->Status == "400") {
34-
httpResponse = GetHttpOutgoingResponsePtr(Request, Settings, IsAjaxRequest);
35+
TContext context(Request);
36+
httpResponse = GetHttpOutgoingResponsePtr(Request, Settings, context);
3537
} else {
3638
httpResponse = Request->CreateResponse( event->Get()->Status, event->Get()->Message, "text/plain", event->Get()->Details);
3739
}

ydb/mvp/oidc_proxy/oidc_proxy_ut.cpp

Lines changed: 22 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include "oidc_session_create_handler.h"
1111
#include "oidc_settings.h"
1212
#include "openid_connect.h"
13+
#include "context.h"
1314

1415
using namespace NMVP::NOIDC;
1516

@@ -719,23 +720,18 @@ Y_UNIT_TEST_SUITE(Mvp) {
719720
TStringBuilder request;
720721
request << "GET /auth/callback?code=code_template&state=" << state << " HTTP/1.1\r\n";
721722
request << "Host: " + hostProxy + "\r\n";
722-
request << "Cookie: " << CreateNameYdbOidcCookie(settings.ClientSecret, wrongState) << "=" << GenerateCookie(wrongState, "/requested/page", settings.ClientSecret, redirectStrategy.IsAjaxRequest()) << "\r\n";
723+
TContext context(wrongState, "/requested/page", redirectStrategy.IsAjaxRequest());
724+
request << "Cookie: " << context.CreateYdbOidcCookie(settings.ClientSecret) << "\r\n";
723725
NHttp::THttpIncomingRequestPtr incomingRequest = new NHttp::THttpIncomingRequest();
724726
EatWholeString(incomingRequest, redirectStrategy.CreateRequest(request));
725727
incomingRequest->Endpoint->Secure = true;
726728
runtime.Send(new IEventHandle(sessionCreator, edge, new NHttp::TEvHttpProxy::TEvHttpIncomingRequest(incomingRequest)));
727729

728730
TAutoPtr<IEventHandle> handle;
729731
NHttp::TEvHttpProxy::TEvHttpOutgoingResponse* outgoingResponseEv = runtime.GrabEdgeEvent<NHttp::TEvHttpProxy::TEvHttpOutgoingResponse>(handle);
730-
UNIT_ASSERT_STRINGS_EQUAL(outgoingResponseEv->Response->Status, "302");
731-
const NHttp::THeaders headers(outgoingResponseEv->Response->Headers);
732-
UNIT_ASSERT(headers.Has("Location"));
733-
TString location = TString(headers.Get("Location"));
734-
UNIT_ASSERT_STRING_CONTAINS(location, "https://auth.test.net/oauth/authorize");
735-
UNIT_ASSERT_STRING_CONTAINS(location, "response_type=code");
736-
UNIT_ASSERT_STRING_CONTAINS(location, "scope=openid");
737-
UNIT_ASSERT_STRING_CONTAINS(location, "client_id=" + settings.ClientId);
738-
UNIT_ASSERT_STRING_CONTAINS(location, "redirect_uri=https://" + hostProxy + "/auth/callback");
732+
UNIT_ASSERT_STRINGS_EQUAL(outgoingResponseEv->Response->Status, "400");
733+
UNIT_ASSERT_STRING_CONTAINS(outgoingResponseEv->Response->Body, "Unknown error has occurred. Please open the page again");
734+
739735
}
740736

741737
Y_UNIT_TEST(OpenIdConnectotWrongStateAuthorizationFlow) {
@@ -773,8 +769,8 @@ Y_UNIT_TEST_SUITE(Mvp) {
773769
TStringBuilder request;
774770
request << "GET /auth/callback?code=code_template&state=" << state << " HTTP/1.1\r\n";
775771
request << "Host: oidcproxy.net\r\n";
776-
const TString oidcCookie = CreateNameYdbOidcCookie(settings.ClientSecret, state);
777-
request << "Cookie: " << oidcCookie << "=" << GenerateCookie(state, "/requested/page", settings.ClientSecret, false) << "\r\n\r\n";
772+
TContext context(state, "/requested/page", false);
773+
request << "Cookie: " << context.CreateYdbOidcCookie(settings.ClientSecret) << "\r\n\r\n";
778774
NHttp::THttpIncomingRequestPtr incomingRequest = new NHttp::THttpIncomingRequest();
779775
EatWholeString(incomingRequest, request);
780776
runtime.Send(new IEventHandle(sessionCreator, edge, new NHttp::TEvHttpProxy::TEvHttpIncomingRequest(incomingRequest)));
@@ -823,7 +819,8 @@ Y_UNIT_TEST_SUITE(Mvp) {
823819
TStringBuilder request;
824820
request << "GET /auth/callback?code=code_template&state=" << state << " HTTP/1.1\r\n";
825821
request << "Host: oidcproxy.net\r\n";
826-
request << "Cookie: " << CreateNameYdbOidcCookie(settings.ClientSecret, state) << "=" << GenerateCookie(state, "/requested/page", settings.ClientSecret, redirectStrategy.IsAjaxRequest()) << "\r\n";
822+
TContext context(state, "/requested/page", redirectStrategy.IsAjaxRequest());
823+
request << "Cookie: " << context.CreateYdbOidcCookie(settings.ClientSecret) << "\r\n";
827824
NHttp::THttpIncomingRequestPtr incomingRequest = new NHttp::THttpIncomingRequest();
828825
EatWholeString(incomingRequest, redirectStrategy.CreateRequest(request));
829826
incomingRequest->Endpoint->Secure = true;
@@ -843,22 +840,11 @@ Y_UNIT_TEST_SUITE(Mvp) {
843840
"Content-Length: " + ToString(authorizationServerResponse.length()) + "\r\n\r\n" + authorizationServerResponse);
844841
runtime.Send(new IEventHandle(handle->Sender, edge, new NHttp::TEvHttpProxy::TEvHttpIncomingResponse(outgoingRequestEv->Request, incomingResponse)));
845842
auto outgoingResponseEv = runtime.GrabEdgeEvent<NHttp::TEvHttpProxy::TEvHttpOutgoingResponse>(handle);
846-
redirectStrategy.CheckRedirectStatus(outgoingResponseEv);
847-
TString location = redirectStrategy.GetRedirectUrl(outgoingResponseEv);
848-
UNIT_ASSERT_STRING_CONTAINS(location, "https://auth.test.net/oauth/authorize");
849-
UNIT_ASSERT_STRING_CONTAINS(location, "response_type=code");
850-
UNIT_ASSERT_STRING_CONTAINS(location, "scope=openid");
851-
UNIT_ASSERT_STRING_CONTAINS(location, "client_id=" + settings.ClientId);
852-
UNIT_ASSERT_STRING_CONTAINS(location, "redirect_uri=https://oidcproxy.net/auth/callback");
853-
854-
NHttp::TUrlParameters urlParameters(location);
855-
const TString newState = urlParameters["state"];
856-
857-
NHttp::THeaders headers(outgoingResponseEv->Response->Headers);
858-
UNIT_ASSERT(headers.Has("Set-Cookie"));
859-
const TStringBuf setCookie = headers.Get("Set-Cookie");
860-
UNIT_ASSERT_STRING_CONTAINS(setCookie, CreateNameYdbOidcCookie(settings.ClientSecret, newState));
861-
redirectStrategy.CheckSpecificHeaders(headers);
843+
UNIT_ASSERT_STRINGS_EQUAL(outgoingResponseEv->Response->Status, "302");
844+
const NHttp::THeaders headers(outgoingResponseEv->Response->Headers);
845+
UNIT_ASSERT(headers.Has("Location"));
846+
TStringBuf location = headers.Get("Location");
847+
UNIT_ASSERT_STRING_CONTAINS(location, "/requested/page");
862848
}
863849

864850
Y_UNIT_TEST(OpenIdConnectSessionServiceCreateAccessTokenInvalid) {
@@ -896,8 +882,8 @@ Y_UNIT_TEST_SUITE(Mvp) {
896882
TStringBuilder request;
897883
request << "GET /callback?code=code_template&state=" << state << " HTTP/1.1\r\n";
898884
request << "Host: oidcproxy.net\r\n";
899-
const TString oidcCookie = CreateNameYdbOidcCookie(settings.ClientSecret, state);
900-
request << "Cookie: " << oidcCookie << "=" << GenerateCookie(state, "/requested/page", settings.ClientSecret, false) << "\r\n\r\n";
885+
TContext context(state, "/requested/page", false);
886+
request << "Cookie: " << context.CreateYdbOidcCookie(settings.ClientSecret) << "\r\n\r\n";
901887
NHttp::THttpIncomingRequestPtr incomingRequest = new NHttp::THttpIncomingRequest();
902888
EatWholeString(incomingRequest, request);
903889
runtime.Send(new IEventHandle(sessionCreator, edge, new NHttp::TEvHttpProxy::TEvHttpIncomingRequest(incomingRequest)));
@@ -941,14 +927,14 @@ Y_UNIT_TEST_SUITE(Mvp) {
941927
std::unique_ptr<grpc::Server> sessionServer(builder.BuildAndStart());
942928

943929
const NActors::TActorId sessionCreator = runtime.Register(new TSessionCreateHandler(edge, settings));
944-
TStringBuf firstRequestState = "first_request_state";
945-
TStringBuf secondRequestState = "second_request_state";
946-
TString firstCookie {CreateNameYdbOidcCookie(settings.ClientSecret, firstRequestState) + "=" + GenerateCookie(firstRequestState, "/requested/page", settings.ClientSecret, redirectStrategy.IsAjaxRequest())};
947-
TString secondCookie {CreateNameYdbOidcCookie(settings.ClientSecret, secondRequestState) + "=" + GenerateCookie(secondRequestState, "/requested/page", settings.ClientSecret, redirectStrategy.IsAjaxRequest())};
930+
TString firstRequestState = "first_request_state";
931+
TString secondRequestState = "second_request_state";
932+
TContext context1(firstRequestState, "/requested/page", redirectStrategy.IsAjaxRequest());
933+
TContext context2(secondRequestState, "/requested/page", redirectStrategy.IsAjaxRequest());
948934
TStringBuilder request;
949935
request << "GET /auth/callback?code=code_template&state=" << firstRequestState << " HTTP/1.1\r\n";
950936
request << "Host: oidcproxy.net\r\n";
951-
request << "Cookie: " << firstCookie << "; " << secondCookie << "\r\n";
937+
request << "Cookie: " << context1.CreateYdbOidcCookie(settings.ClientSecret) << "; " << context2.CreateYdbOidcCookie(settings.ClientSecret) << "\r\n";
952938
NHttp::THttpIncomingRequestPtr incomingRequest = new NHttp::THttpIncomingRequest();
953939
EatWholeString(incomingRequest, redirectStrategy.CreateRequest(request));
954940
incomingRequest->Endpoint->Secure = true;

0 commit comments

Comments
 (0)