|
1 | 1 | #include "source/extensions/filters/http/proto_api_scrubber/filter_config.h"
|
2 | 2 |
|
| 3 | +#include <algorithm> |
| 4 | + |
| 5 | +#include "envoy/extensions/filters/http/proto_api_scrubber/v3/matcher_actions.pb.h" |
| 6 | +#include "envoy/matcher/matcher.h" |
| 7 | + |
| 8 | +#include "source/common/matcher/matcher.h" |
| 9 | + |
3 | 10 | namespace Envoy {
|
4 | 11 | namespace Extensions {
|
5 | 12 | namespace HttpFilters {
|
6 | 13 | namespace ProtoApiScrubber {
|
7 | 14 | namespace {
|
8 | 15 |
|
9 |
| -using ::envoy::extensions::filters::http::proto_api_scrubber::v3::ProtoApiScrubberConfig; |
| 16 | +static constexpr absl::string_view kConfigInitializationError = |
| 17 | + "Error encountered during config initialization."; |
| 18 | + |
| 19 | +// Returns whether the fully qualified `api_name` is valid or not. |
| 20 | +// Checks for separator '.' in the name and verifies each substring between these separators are |
| 21 | +// non-empty. Note that it does not verify whether the API actually exists or not. |
| 22 | +bool isApiNameValid(absl::string_view api_name) { |
| 23 | + const std::vector<absl::string_view> api_name_parts = absl::StrSplit(api_name, '.'); |
| 24 | + if (api_name_parts.size() <= 1) { |
| 25 | + return false; |
| 26 | + } |
| 27 | + |
| 28 | + // Returns true if all of the api_name_parts are non-empty, otherwise returns false. |
| 29 | + return !std::any_of(api_name_parts.cbegin(), api_name_parts.cend(), |
| 30 | + [](const absl::string_view s) { return s.empty(); }); |
| 31 | +} |
| 32 | + |
10 | 33 | } // namespace
|
11 | 34 |
|
12 |
| -FilterConfig::FilterConfig(const ProtoApiScrubberConfig&) {} |
| 35 | +absl::StatusOr<std::shared_ptr<const ProtoApiScrubberFilterConfig>> |
| 36 | +ProtoApiScrubberFilterConfig::create(const ProtoApiScrubberConfig& proto_config, |
| 37 | + Server::Configuration::FactoryContext& context) { |
| 38 | + std::shared_ptr<ProtoApiScrubberFilterConfig> filter_config_ptr = |
| 39 | + std::shared_ptr<ProtoApiScrubberFilterConfig>(new ProtoApiScrubberFilterConfig()); |
| 40 | + RETURN_IF_ERROR(filter_config_ptr->initialize(proto_config, context)); |
| 41 | + return filter_config_ptr; |
| 42 | +} |
| 43 | + |
| 44 | +absl::Status |
| 45 | +ProtoApiScrubberFilterConfig::initialize(const ProtoApiScrubberConfig& proto_config, |
| 46 | + Server::Configuration::FactoryContext& context) { |
| 47 | + ENVOY_LOG(trace, "Initializing filter config from the proto config: {}", |
| 48 | + proto_config.DebugString()); |
| 49 | + |
| 50 | + FilteringMode filtering_mode = proto_config.filtering_mode(); |
| 51 | + RETURN_IF_ERROR(validateFilteringMode(filtering_mode)); |
| 52 | + filtering_mode_ = filtering_mode; |
| 53 | + |
| 54 | + for (const auto& method_restriction : proto_config.restrictions().method_restrictions()) { |
| 55 | + std::string method_name = method_restriction.first; |
| 56 | + RETURN_IF_ERROR(validateMethodName(method_name)); |
| 57 | + RETURN_IF_ERROR(initializeMethodRestrictions( |
| 58 | + method_name, request_field_restrictions_, |
| 59 | + method_restriction.second.request_field_restrictions(), context)); |
| 60 | + RETURN_IF_ERROR(initializeMethodRestrictions( |
| 61 | + method_name, response_field_restrictions_, |
| 62 | + method_restriction.second.response_field_restrictions(), context)); |
| 63 | + } |
| 64 | + |
| 65 | + ENVOY_LOG(trace, "Filter config initialized successfully."); |
| 66 | + return absl::OkStatus(); |
| 67 | +} |
| 68 | + |
| 69 | +absl::Status ProtoApiScrubberFilterConfig::validateFilteringMode(FilteringMode filtering_mode) { |
| 70 | + switch (filtering_mode) { |
| 71 | + case FilteringMode::ProtoApiScrubberConfig_FilteringMode_OVERRIDE: |
| 72 | + return absl::OkStatus(); |
| 73 | + default: |
| 74 | + return absl::InvalidArgumentError( |
| 75 | + fmt::format("{} Unsupported 'filtering_mode': {}.", kConfigInitializationError, |
| 76 | + envoy::extensions::filters::http::proto_api_scrubber::v3:: |
| 77 | + ProtoApiScrubberConfig_FilteringMode_Name(filtering_mode))); |
| 78 | + } |
| 79 | +} |
| 80 | + |
| 81 | +absl::Status ProtoApiScrubberFilterConfig::validateMethodName(absl::string_view method_name) { |
| 82 | + if (method_name.empty()) { |
| 83 | + return absl::InvalidArgumentError( |
| 84 | + fmt::format("{} Invalid method name: '{}'. Method name is empty.", |
| 85 | + kConfigInitializationError, method_name)); |
| 86 | + } |
| 87 | + |
| 88 | + if (absl::StrContains(method_name, '*')) { |
| 89 | + return absl::InvalidArgumentError(fmt::format( |
| 90 | + "{} Invalid method name: '{}'. Method name contains '*' which is not supported.", |
| 91 | + kConfigInitializationError, method_name)); |
| 92 | + } |
| 93 | + |
| 94 | + const std::vector<absl::string_view> method_name_parts = absl::StrSplit(method_name, '/'); |
| 95 | + if (method_name_parts.size() != 3 || !method_name_parts[0].empty() || |
| 96 | + method_name_parts[1].empty() || !isApiNameValid(method_name_parts[1]) || |
| 97 | + method_name_parts[2].empty()) { |
| 98 | + return absl::InvalidArgumentError( |
| 99 | + fmt::format("{} Invalid method name: '{}'. Method name should follow the gRPC format " |
| 100 | + "('/package.ServiceName/MethodName').", |
| 101 | + kConfigInitializationError, method_name)); |
| 102 | + } |
| 103 | + |
| 104 | + return absl::OkStatus(); |
| 105 | +} |
| 106 | + |
| 107 | +absl::Status ProtoApiScrubberFilterConfig::validateFieldMask(absl::string_view field_mask) { |
| 108 | + if (field_mask.empty()) { |
| 109 | + return absl::InvalidArgumentError( |
| 110 | + fmt::format("{} Invalid field mask: '{}'. Field mask is empty.", kConfigInitializationError, |
| 111 | + field_mask)); |
| 112 | + } |
| 113 | + |
| 114 | + if (absl::StrContains(field_mask, '*')) { |
| 115 | + return absl::InvalidArgumentError( |
| 116 | + fmt::format("{} Invalid field mask: '{}'. Field mask contains '*' which is not supported.", |
| 117 | + kConfigInitializationError, field_mask)); |
| 118 | + } |
| 119 | + |
| 120 | + return absl::OkStatus(); |
| 121 | +} |
| 122 | + |
| 123 | +absl::Status ProtoApiScrubberFilterConfig::initializeMethodRestrictions( |
| 124 | + absl::string_view method_name, StringPairToMatchTreeMap& field_restrictions, |
| 125 | + const Map<std::string, RestrictionConfig>& restrictions, |
| 126 | + Envoy::Server::Configuration::FactoryContext& context) { |
| 127 | + for (const auto& restriction : restrictions) { |
| 128 | + absl::string_view field_mask = restriction.first; |
| 129 | + RETURN_IF_ERROR(validateFieldMask(field_mask)); |
| 130 | + ProtoApiScrubberRemoveFieldAction remove_field_action; |
| 131 | + MatcherInputValidatorVisitor validation_visitor; |
| 132 | + Matcher::MatchTreeFactory<HttpMatchingData, ProtoApiScrubberRemoveFieldAction> matcher_factory( |
| 133 | + remove_field_action, context.serverFactoryContext(), validation_visitor); |
| 134 | + |
| 135 | + absl::optional<Matcher::MatchTreeFactoryCb<HttpMatchingData>> factory_cb = |
| 136 | + matcher_factory.create(restriction.second.matcher()); |
| 137 | + if (factory_cb.has_value()) { |
| 138 | + field_restrictions[std::make_pair(std::string(method_name), std::string(field_mask))] = |
| 139 | + factory_cb.value()(); |
| 140 | + } else { |
| 141 | + return absl::InvalidArgumentError(fmt::format( |
| 142 | + "{} Failed to initialize matcher factory callback for method {} and field mask {}.", |
| 143 | + kConfigInitializationError, method_name, field_mask)); |
| 144 | + } |
| 145 | + } |
| 146 | + |
| 147 | + return absl::OkStatus(); |
| 148 | +} |
| 149 | + |
| 150 | +MatchTreeHttpMatchingDataSharedPtr |
| 151 | +ProtoApiScrubberFilterConfig::getRequestFieldMatcher(const std::string& method_name, |
| 152 | + const std::string& field_mask) const { |
| 153 | + if (auto it = request_field_restrictions_.find(std::make_pair(method_name, field_mask)); |
| 154 | + it != request_field_restrictions_.end()) { |
| 155 | + return it->second; |
| 156 | + } |
| 157 | + |
| 158 | + return nullptr; |
| 159 | +} |
| 160 | + |
| 161 | +MatchTreeHttpMatchingDataSharedPtr |
| 162 | +ProtoApiScrubberFilterConfig::getResponseFieldMatcher(const std::string& method_name, |
| 163 | + const std::string& field_mask) const { |
| 164 | + if (auto it = response_field_restrictions_.find(std::make_pair(method_name, field_mask)); |
| 165 | + it != response_field_restrictions_.end()) { |
| 166 | + return it->second; |
| 167 | + } |
| 168 | + |
| 169 | + return nullptr; |
| 170 | +} |
| 171 | + |
| 172 | +REGISTER_FACTORY(RemoveFilterActionFactory, |
| 173 | + Matcher::ActionFactory<ProtoApiScrubberRemoveFieldAction>); |
13 | 174 |
|
14 | 175 | } // namespace ProtoApiScrubber
|
15 | 176 | } // namespace HttpFilters
|
|
0 commit comments