Skip to content

Commit

Permalink
implement jwt normalize claims
Browse files Browse the repository at this point in the history
Signed-off-by: Kuat Yessenov <[email protected]>
  • Loading branch information
kyessenov committed Oct 27, 2023
1 parent e9366f2 commit 0d827df
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,6 @@ message JwtProvider {
//
string payload_in_metadata = 9;

// [#not-implemented-hide:]
// Normalizes the payload representation in the request metadata.
NormalizePayload normalize_payload_in_metadata = 18;

Expand Down
28 changes: 26 additions & 2 deletions source/extensions/filters/http/jwt_authn/authenticator.cc
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "source/common/protobuf/protobuf.h"
#include "source/common/tracing/http_tracer_impl.h"

#include "absl/strings/str_split.h"
#include "jwt_verify_lib/jwt.h"
#include "jwt_verify_lib/struct_utils.h"
#include "jwt_verify_lib/verify.h"
Expand Down Expand Up @@ -76,6 +77,9 @@ class AuthenticatorImpl : public Logger::Loggable<Logger::Id::jwt>,
// Handle Good Jwt either Cache JWT or verified public key.
void handleGoodJwt(bool cache_hit);

// Normalize and set the payload metadata.
void setPayloadMetadata(const ProtobufWkt::Struct& jwt_payload);

// Calls the callback with status.
void doneWithStatus(const Status& status);

Expand Down Expand Up @@ -373,9 +377,8 @@ void AuthenticatorImpl::handleGoodJwt(bool cache_hit) {
if (!provider.header_in_metadata().empty()) {
set_extracted_jwt_data_cb_(provider.header_in_metadata(), jwt_->header_pb_);
}

if (!provider.payload_in_metadata().empty()) {
set_extracted_jwt_data_cb_(provider.payload_in_metadata(), jwt_->payload_pb_);
setPayloadMetadata(jwt_->payload_pb_);
}
}
if (provider_ && !cache_hit) {
Expand All @@ -385,6 +388,27 @@ void AuthenticatorImpl::handleGoodJwt(bool cache_hit) {
doneWithStatus(Status::Ok);
}

void AuthenticatorImpl::setPayloadMetadata(const ProtobufWkt::Struct& jwt_payload) {
const auto& provider = jwks_data_->getJwtProvider();
const auto& normalize = provider.normalize_payload_in_metadata();
if (normalize.space_delimited_claims().size() == 0) {
set_extracted_jwt_data_cb_(provider.payload_in_metadata(), jwt_payload);
}
// Make a temporary copy to normalize the JWT struct.
ProtobufWkt::Struct out_payload = jwt_payload;
for (const auto& claim : normalize.space_delimited_claims()) {
const auto& it = jwt_payload.fields().find(claim);
if (it != jwt_payload.fields().end() && it->second.has_string_value()) {
const auto list = absl::StrSplit(it->second.string_value(), ' ', absl::SkipEmpty());
for (const auto& elt : list) {
(*out_payload.mutable_fields())[claim].mutable_list_value()->add_values()->set_string_value(
elt);
}
}
}
set_extracted_jwt_data_cb_(provider.payload_in_metadata(), out_payload);
}

void AuthenticatorImpl::doneWithStatus(const Status& status) {
ENVOY_LOG(debug, "{}: JWT token verification completed with: {}", name(),
::google::jwt_verify::getStatusString(status));
Expand Down
32 changes: 32 additions & 0 deletions test/extensions/filters/http/jwt_authn/authenticator_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,38 @@ TEST_F(AuthenticatorTest, TestSetPayload) {
TestUtility::protoEqual(expected_payload, out_extracted_data_.fields().at("my_payload")));
}

// This test verifies the JWT payload is set.
TEST_F(AuthenticatorTest, TestSetPayloadWithSpaces) {
// Config payload_in_metadata flag
(*proto_config_.mutable_providers())[std::string(ProviderName)].set_payload_in_metadata(
"my_payload");
auto* normalize_payload = (*proto_config_.mutable_providers())[std::string(ProviderName)]
.mutable_normalize_payload_in_metadata();
normalize_payload->add_space_delimited_claims("scope");
normalize_payload->add_space_delimited_claims("test_string");
normalize_payload->add_space_delimited_claims("test_num");

createAuthenticator();
EXPECT_CALL(*raw_fetcher_, fetch(_, _))
.WillOnce(Invoke([this](Tracing::Span&, JwksFetcher::JwksReceiver& receiver) {
receiver.onJwksSuccess(std::move(jwks_));
}));

// Test OK pubkey and its cache
Http::TestRequestHeaderMapImpl headers{
{"Authorization", "Bearer " + std::string(GoodTokenWithSpaces)}};

expectVerifyStatus(Status::Ok, headers);

// Only one field is set.
EXPECT_EQ(1, out_extracted_data_.fields().size());

ProtobufWkt::Value expected_payload;
TestUtility::loadFromJson(ExpectedPayloadJSONWithSpaces, expected_payload);
EXPECT_TRUE(
TestUtility::protoEqual(expected_payload, out_extracted_data_.fields().at("my_payload")));
}

// This test verifies setting only the extracted header to metadata.
TEST_F(AuthenticatorTest, TestSetHeader) {
// Set the extracted header to metadata.
Expand Down
33 changes: 33 additions & 0 deletions test/extensions/filters/http/jwt_authn/test_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,26 @@ const char GoodToken[] = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwc
"EprqSZUzi_ZzzYzqBNVhIJujcNWij7JRra2sXXiSAfKjtxHQoxrX8n4V1ySWJ3_1T"
"H_cJcdfS_RKP7YgXRWC0L16PNF5K7iqRqmjKALNe83ZFnFIw";

// Payload:
// {
// "iss": "https://example.com",
// "sub": "[email protected]",
// "exp": 2001001001,
// "aud": "example_service",
// "scope": "read write",
// "test_string": "test_value",
// "test_num": 1337
// }
const char GoodTokenWithSpaces[] =
"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9."
"eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoidGVzdEBleGFtcGxlLmNvbSIsImV4cCI6MjAwMTAwMTAwMS"
"wiYXVkIjoiZXhhbXBsZV9zZXJ2aWNlIiwic2NvcGUiOiJyZWFkIHdyaXRlIiwidGVzdF9zdHJpbmciOiJ0ZXN0X3ZhbHVl"
"IiwidGVzdF9udW0iOjEzMzd9.cKTwWSJgS0TZ3Ajc9QrAA50Me7j1zVv9YzDT_"
"2UE5jlCs5vWkdWjUb2r7MYaqximXj3affDZdDsUxMaqqR7lWT2EbxOoEceBkCMmakgSs8tjZ210w0YTU0OyhrrxsyxUpsp"
"PeRzPIHQTUdN7zU_KkMcUU1yDSlnJxqlYXyTL9E-DhTnLwoOdgFGiQs-md_QJfdOFgXQqU71EZ-"
"Ofxen8EFl10wbzHubMHGLJqVfFzK-iuVr2P0OZ0ymWvPGwQdlVMojHx3P0Yb8MRbhdW04hCJq-_"
"fTE1RNb6ja1JBFQbyGcQTtWVSdkHZ_C8syd8s-aK4C8_VhwNEDviOVrHPbztw";

// Payload:
// {"iss":"https://example.com","sub":"[email protected]","exp":null}
const char NonExpiringToken[] =
Expand Down Expand Up @@ -271,6 +291,19 @@ const char ExpectedPayloadJSON[] = R"(
}
)";

// Base64 decoded Payload with space-delimited claims JSON
const char ExpectedPayloadJSONWithSpaces[] = R"(
{
"iss":"https://example.com",
"sub":"[email protected]",
"exp":2001001001,
"aud":"example_service",
"scope":["read","write"],
"test_string":["test_value"],
"test_num":1337
}
)";

const char ExpectedHeaderJSON[] = R"(
{
"alg": "RS256",
Expand Down

0 comments on commit 0d827df

Please sign in to comment.