Skip to content

Commit 1f48795

Browse files
tanx16copybara-github
authored andcommitted
Enable meta-tagging for redaction purposes
See https://protobuf.dev/news/2024-12-04/ PiperOrigin-RevId: 721507009
1 parent 080a983 commit 1f48795

File tree

5 files changed

+221
-4
lines changed

5 files changed

+221
-4
lines changed

src/google/protobuf/BUILD.bazel

+2
Original file line numberDiff line numberDiff line change
@@ -878,6 +878,7 @@ filegroup(
878878
"unittest_proto3_extensions.proto",
879879
"unittest_proto3_lite.proto",
880880
"unittest_proto3_optional.proto",
881+
"unittest_redaction.proto",
881882
"unittest_retention.proto",
882883
"unittest_string_type.proto",
883884
"unittest_well_known_types.proto",
@@ -961,6 +962,7 @@ proto_library(
961962
"unittest_proto3_bad_macros.proto",
962963
"unittest_proto3_extensions.proto",
963964
"unittest_proto3_lite.proto",
965+
"unittest_redaction.proto",
964966
"unittest_retention.proto",
965967
"unittest_string_type.proto",
966968
"unittest_string_view.proto",

src/google/protobuf/text_format.cc

+61-4
Original file line numberDiff line numberDiff line change
@@ -3026,12 +3026,70 @@ void TextFormat::Printer::PrintUnknownFields(
30263026
}
30273027
}
30283028

3029+
// Traverse the tree of field options and check if any of them are sensitive.
3030+
// We check for sensitive enum values in the options and in the fields of the
3031+
// message-type options, recursively.
3032+
TextFormat::RedactionState TextFormat::IsOptionSensitive(
3033+
const Message& opts, const Reflection* reflection,
3034+
const FieldDescriptor* option) {
3035+
if (option->type() == FieldDescriptor::TYPE_ENUM) {
3036+
auto count =
3037+
option->is_repeated() ? reflection->FieldSize(opts, option) : 1;
3038+
for (auto i = 0; i < count; i++) {
3039+
int enum_val = option->is_repeated()
3040+
? reflection->GetRepeatedEnumValue(opts, option, i)
3041+
: reflection->GetEnumValue(opts, option);
3042+
const EnumValueDescriptor* option_value =
3043+
option->enum_type()->FindValueByNumber(enum_val);
3044+
if (option_value->options().debug_redact()) {
3045+
return TextFormat::RedactionState{true, false};
3046+
}
3047+
}
3048+
} else if (option->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) {
3049+
auto count =
3050+
option->is_repeated() ? reflection->FieldSize(opts, option) : 1;
3051+
for (auto i = 0; i < count; i++) {
3052+
const Message& sub_message =
3053+
option->is_repeated()
3054+
? reflection->GetRepeatedMessage(opts, option, i)
3055+
: reflection->GetMessage(opts, option);
3056+
const Reflection* sub_reflection = sub_message.GetReflection();
3057+
std::vector<const FieldDescriptor*> message_fields;
3058+
sub_reflection->ListFields(sub_message, &message_fields);
3059+
for (const FieldDescriptor* message_field : message_fields) {
3060+
auto result = TextFormat::IsOptionSensitive(sub_message, sub_reflection,
3061+
message_field);
3062+
if (result.redact) {
3063+
return result;
3064+
}
3065+
}
3066+
}
3067+
}
3068+
return TextFormat::RedactionState{false, false};
3069+
}
3070+
3071+
TextFormat::RedactionState TextFormat::GetRedactionState(
3072+
const FieldDescriptor* field) {
3073+
auto options = field->options();
3074+
auto state = TextFormat::RedactionState{options.debug_redact(), false};
3075+
std::vector<const FieldDescriptor*> field_options;
3076+
const Reflection* reflection = options.GetReflection();
3077+
reflection->ListFields(options, &field_options);
3078+
for (const FieldDescriptor* option : field_options) {
3079+
auto result = TextFormat::IsOptionSensitive(options, reflection, option);
3080+
state = TextFormat::RedactionState{state.redact || result.redact,
3081+
state.report || result.report};
3082+
}
3083+
return state;
3084+
}
30293085
bool TextFormat::Printer::TryRedactFieldValue(
30303086
const Message& message, const FieldDescriptor* field,
30313087
BaseTextGenerator* generator, bool insert_value_separator) const {
3032-
RedactionState redaction_state = field->options().debug_redact()
3033-
? RedactionState{true, false}
3034-
: RedactionState{false, false};
3088+
TextFormat::RedactionState redaction_state =
3089+
field->file()->pool()->MemoizeProjection(
3090+
field, [](const FieldDescriptor* field) {
3091+
return TextFormat::GetRedactionState(field);
3092+
});
30353093
if (redaction_state.redact) {
30363094
if (redact_debug_string_) {
30373095
IncrementRedactedFieldCounter();
@@ -3051,7 +3109,6 @@ bool TextFormat::Printer::TryRedactFieldValue(
30513109
}
30523110
return false;
30533111
}
3054-
30553112
} // namespace protobuf
30563113
} // namespace google
30573114

src/google/protobuf/text_format.h

+6
Original file line numberDiff line numberDiff line change
@@ -622,6 +622,12 @@ class PROTOBUF_EXPORT TextFormat {
622622
bool report;
623623
};
624624

625+
static TextFormat::RedactionState GetRedactionState(
626+
const FieldDescriptor* field);
627+
628+
static TextFormat::RedactionState IsOptionSensitive(
629+
const Message& opts, const Reflection* reflection,
630+
const FieldDescriptor* option);
625631
// Data structure which is populated with the locations of each field
626632
// value parsed from the text.
627633
class PROTOBUF_EXPORT ParseInfoTree {

src/google/protobuf/text_format_unittest.cc

+80
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
#include "google/protobuf/unittest_mset.pb.h"
5050
#include "google/protobuf/unittest_mset_wire_format.pb.h"
5151
#include "google/protobuf/unittest_proto3.pb.h"
52+
#include "google/protobuf/unittest_redaction.pb.h"
5253
#include "utf8_validity.h"
5354

5455

@@ -2679,6 +2680,85 @@ TEST(TextFormatUnknownFieldTest, TestUnknownExtension) {
26792680
}
26802681

26812682

2683+
TEST(AbslStringifyTest, TextFormatIsUnchanged) {
2684+
unittest::TestAllTypes proto;
2685+
proto.set_optional_int32(1);
2686+
proto.set_optional_string("foo");
2687+
2688+
std::string text;
2689+
ASSERT_TRUE(TextFormat::PrintToString(proto, &text));
2690+
EXPECT_EQ(
2691+
"optional_int32: 1\n"
2692+
"optional_string: \"foo\"\n",
2693+
text);
2694+
}
2695+
2696+
TEST(AbslStringifyTest, StringifyHasRedactionMarker) {
2697+
unittest::TestAllTypes proto;
2698+
proto.set_optional_int32(1);
2699+
proto.set_optional_string("foo");
2700+
2701+
EXPECT_THAT(absl::StrCat(proto), testing::MatchesRegex(
2702+
"optional_int32: 1\n"
2703+
"optional_string: \"foo\"\n"));
2704+
}
2705+
2706+
2707+
TEST(AbslStringifyTest, StringifyMetaAnnotatedIsRedacted) {
2708+
unittest::TestRedactedMessage proto;
2709+
proto.set_meta_annotated("foo");
2710+
EXPECT_THAT(absl::StrCat(proto), testing::MatchesRegex(absl::Substitute(
2711+
"meta_annotated: $0\n",
2712+
value_replacement)));
2713+
}
2714+
2715+
TEST(AbslStringifyTest, StringifyRepeatedMetaAnnotatedIsRedacted) {
2716+
unittest::TestRedactedMessage proto;
2717+
proto.set_repeated_meta_annotated("foo");
2718+
EXPECT_THAT(absl::StrCat(proto), testing::MatchesRegex(absl::Substitute(
2719+
"repeated_meta_annotated: $0\n",
2720+
value_replacement)));
2721+
}
2722+
2723+
TEST(AbslStringifyTest, StringifyRepeatedMetaAnnotatedIsNotRedacted) {
2724+
unittest::TestRedactedMessage proto;
2725+
proto.set_unredacted_repeated_annotations("foo");
2726+
EXPECT_THAT(absl::StrCat(proto),
2727+
testing::MatchesRegex(
2728+
"unredacted_repeated_annotations: \"foo\"\n"));
2729+
}
2730+
2731+
TEST(AbslStringifyTest, TextFormatMetaAnnotatedIsNotRedacted) {
2732+
unittest::TestRedactedMessage proto;
2733+
proto.set_meta_annotated("foo");
2734+
std::string text;
2735+
ASSERT_TRUE(TextFormat::PrintToString(proto, &text));
2736+
EXPECT_EQ("meta_annotated: \"foo\"\n", text);
2737+
}
2738+
TEST(AbslStringifyTest, StringifyDirectMessageEnumIsRedacted) {
2739+
unittest::TestRedactedMessage proto;
2740+
proto.set_test_direct_message_enum("foo");
2741+
EXPECT_THAT(absl::StrCat(proto), testing::MatchesRegex(absl::Substitute(
2742+
"test_direct_message_enum: $0\n",
2743+
value_replacement)));
2744+
}
2745+
TEST(AbslStringifyTest, StringifyNestedMessageEnumIsRedacted) {
2746+
unittest::TestRedactedMessage proto;
2747+
proto.set_test_nested_message_enum("foo");
2748+
EXPECT_THAT(absl::StrCat(proto), testing::MatchesRegex(absl::Substitute(
2749+
"test_nested_message_enum: $0\n",
2750+
value_replacement)));
2751+
}
2752+
2753+
TEST(AbslStringifyTest, StringifyRedactedOptionDoesNotRedact) {
2754+
unittest::TestRedactedMessage proto;
2755+
proto.set_test_redacted_message_enum("foo");
2756+
EXPECT_THAT(absl::StrCat(proto),
2757+
testing::MatchesRegex(
2758+
"test_redacted_message_enum: \"foo\"\n"));
2759+
}
2760+
2761+
26822762
TEST(TextFormatFloatingPointTest, PreservesNegative0) {
26832763
proto3_unittest::TestAllTypes in_message;
26842764
in_message.set_optional_float(-0.0f);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Test proto for redaction
2+
edition = "2023";
3+
4+
package protobuf_unittest;
5+
6+
import "google/protobuf/any.proto";
7+
import "google/protobuf/descriptor.proto";
8+
9+
option java_package = "com.google.protos";
10+
option java_outer_classname = "RedactionProto";
11+
option features.repeated_field_encoding = EXPANDED;
12+
option features.utf8_validation = NONE;
13+
14+
extend .google.protobuf.FieldOptions {
15+
MetaAnnotatedEnum meta_annotated_enum = 535801413;
16+
repeated MetaAnnotatedEnum repeated_meta_annotated_enum = 535801414;
17+
TestNestedMessageEnum test_nested_message_enum = 535801415;
18+
}
19+
20+
message TestRedactedNestMessage {
21+
string foo = 1;
22+
}
23+
24+
message TestRepeatedRedactedNestMessage {
25+
string bar = 1;
26+
}
27+
28+
message TestMessageEnum {
29+
repeated MetaAnnotatedEnum redactable_enum = 1;
30+
}
31+
32+
message TestNestedMessageEnum {
33+
repeated MetaAnnotatedEnum direct_enum = 1;
34+
TestMessageEnum nested_enum = 2;
35+
string redacted_string = 3 [debug_redact = true];
36+
}
37+
38+
message TestRedactedMessage {
39+
string text_field = 1 [deprecated = true];
40+
string meta_annotated = 8 [(meta_annotated_enum) = TEST_REDACTABLE];
41+
string repeated_meta_annotated = 9 [
42+
(protobuf_unittest.repeated_meta_annotated_enum) = TEST_NO_REDACT,
43+
(protobuf_unittest.repeated_meta_annotated_enum) = TEST_REDACTABLE
44+
];
45+
string unredacted_repeated_annotations = 10 [
46+
(protobuf_unittest.repeated_meta_annotated_enum) = TEST_NO_REDACT,
47+
(protobuf_unittest.repeated_meta_annotated_enum) = TEST_NO_REDACT_AGAIN
48+
];
49+
string unreported_non_meta_debug_redact_field = 17 [debug_redact = true];
50+
google.protobuf.Any any_field = 18 [debug_redact = true];
51+
string redactable_false = 19 [(meta_annotated_enum) = TEST_REDACTABLE_FALSE];
52+
string test_direct_message_enum = 22
53+
[(protobuf_unittest.test_nested_message_enum) = {
54+
direct_enum: [ TEST_NO_REDACT, TEST_REDACTABLE ]
55+
}];
56+
string test_nested_message_enum = 23
57+
[(protobuf_unittest.test_nested_message_enum) = {
58+
nested_enum { redactable_enum: [ TEST_NO_REDACT, TEST_REDACTABLE ] }
59+
}];
60+
string test_redacted_message_enum = 24
61+
[(protobuf_unittest.test_nested_message_enum) = {
62+
redacted_string: "redacted_but_doesnt_redact"
63+
}];
64+
}
65+
66+
enum MetaAnnotatedEnum {
67+
TEST_NULL = 0;
68+
TEST_REDACTABLE = 1 [debug_redact = true];
69+
TEST_NO_REDACT = 2;
70+
TEST_NO_REDACT_AGAIN = 3;
71+
TEST_REDACTABLE_FALSE = 4 [debug_redact = false];
72+
}

0 commit comments

Comments
 (0)