fgerlits commented on code in PR #1806:
URL: https://github.com/apache/nifi-minifi-cpp/pull/1806#discussion_r1674182950


##########
encrypt-config/EncryptConfigMain.cpp:
##########
@@ -48,6 +48,15 @@ int main(int argc, char* argv[]) try {
   argument_parser.add_argument("--property-value")
       .metavar("VALUE")
       .help(minifi::utils::string::join_pack("The new value of the sensitive 
property (", OPERATION_FLOW_CONFIG, " only)"));
+  argument_parser.add_argument("--parameter-context-id")
+      .metavar("PARAMETER_CONTEXT_ID")
+      .help(minifi::utils::string::join_pack("Parameter context id (", 
OPERATION_FLOW_CONFIG, " only)"));

Review Comment:
   I think `ID` would be enough as the metavar, otherwise the help text will be 
too long:
   ```
     --parameter-context-id PARAMETER_CONTEXT_ID  Parameter context id 
(flow-config only)
   ```
   vs
   ```
     --parameter-context-id ID             Parameter context id (flow-config 
only)
   ```



##########
encrypt-config/FlowConfigEncryptor.cpp:
##########
@@ -82,58 +100,59 @@ std::vector<SensitiveProperty> 
listSensitiveProperties(const minifi::core::Proce
     gsl_Expects(controller_service_node);
     const auto* controller_service = 
controller_service_node->getControllerServiceImplementation();
     gsl_Expects(controller_service);
+    auto props = controller_service->getProperties();
     if (processed_controller_services.contains(controller_service->getUUID())) 
{
       continue;
     }
     processed_controller_services.insert(controller_service->getUUID());
-    for (const auto& [_, property] : controller_service->getProperties()) {
+    for (const auto& [_, property] : props) {

Review Comment:
   why did this change?



##########
encrypt-config/FlowConfigEncryptor.cpp:
##########
@@ -82,58 +100,59 @@ std::vector<SensitiveProperty> 
listSensitiveProperties(const minifi::core::Proce
     gsl_Expects(controller_service_node);
     const auto* controller_service = 
controller_service_node->getControllerServiceImplementation();
     gsl_Expects(controller_service);
+    auto props = controller_service->getProperties();
     if (processed_controller_services.contains(controller_service->getUUID())) 
{
       continue;
     }
     processed_controller_services.insert(controller_service->getUUID());
-    for (const auto& [_, property] : controller_service->getProperties()) {
+    for (const auto& [_, property] : props) {
       if (property.isSensitive()) {
-        sensitive_properties.push_back(SensitiveProperty{
+        sensitive_items.push_back(SensitiveItem{
             .component_type = ComponentType::ControllerService,
             .component_id = controller_service->getUUID(),
             .component_name = controller_service->getName(),
-            .property_name = property.getName(),
-            .property_display_name = property.getDisplayName(),
-            .property_value = property.getValue().to_string()});
+            .item_name = property.getName(),
+            .item_display_name = property.getDisplayName(),
+            .item_value = property.getValue().to_string()});
       }
     }
   }
 
-  return sensitive_properties;
+  return sensitive_items;
 }
 
-std::unordered_map<minifi::utils::Identifier, minifi::core::flow::Overrides> 
createOverridesInteractively(const std::vector<SensitiveProperty>& 
sensitive_properties) {
+std::unordered_map<minifi::utils::Identifier, minifi::core::flow::Overrides> 
createOverridesInteractively(const std::vector<SensitiveItem>& sensitive_items) 
{
   std::unordered_map<minifi::utils::Identifier, minifi::core::flow::Overrides> 
overrides;
   std::cout << '\n';
-  for (const auto& sensitive_property : sensitive_properties) {
-    std::cout << magic_enum::enum_name(sensitive_property.component_type) << " 
" << sensitive_property.component_name << " (" << 
sensitive_property.component_id.to_string() << ") "
-              << "has sensitive property " << 
sensitive_property.property_display_name << "\n    enter a new value or press 
Enter to keep the current value unchanged: ";
+  for (const auto& sensitive_item : sensitive_items) {
+    std::cout << magic_enum::enum_name(sensitive_item.component_type) << " " 
<< sensitive_item.component_name << " (" << 
sensitive_item.component_id.to_string() << ") "
+              << "has sensitive item " << sensitive_item.item_display_name << 
"\n    enter a new value or press Enter to keep the current value unchanged: ";
     std::cout.flush();
     std::string new_value;
     std::getline(std::cin, new_value);
     if (!new_value.empty()) {
-      
overrides[sensitive_property.component_id].add(sensitive_property.property_name,
 new_value);
+      overrides[sensitive_item.component_id].add(sensitive_item.item_name, 
new_value);
     }
   }
   return overrides;
 }
 
-std::unordered_map<minifi::utils::Identifier, minifi::core::flow::Overrides> 
createOverridesForSingleProperty(
-    const std::vector<SensitiveProperty>& sensitive_properties, const 
std::string& component_id, const std::string& property_name, const std::string& 
property_value) {
-  const auto sensitive_property_it = 
std::ranges::find_if(sensitive_properties, [&](const auto& sensitive_property) {
-    return sensitive_property.component_id.to_string().view() == component_id 
&& (sensitive_property.property_name == property_name || 
sensitive_property.property_display_name == property_name);
+std::unordered_map<minifi::utils::Identifier, minifi::core::flow::Overrides> 
createOverridesForSingleItem(
+    const std::vector<SensitiveItem>& sensitive_items, const std::string& 
component_id, const std::string& item_name, const std::string& item_value) {
+  const auto sensitive_item_it = std::ranges::find_if(sensitive_items, 
[&](const auto& sensitive_item) {
+    return sensitive_item.component_id.to_string().view() == component_id && 
(sensitive_item.item_name == item_name || sensitive_item.item_display_name == 
item_name);
   });
-  if (sensitive_property_it == sensitive_properties.end()) {
-    std::cout << "No sensitive property found with this component ID and 
property name.\n";
+  if (sensitive_item_it == sensitive_items.end()) {
+    std::cout << "No sensitive item found with this component ID and item 
name.\n";

Review Comment:
   Since we never say "item" in the help text, I would keep this term internal, 
and use "property or parameter" in user-facing messages.
   ```suggestion
       std::cout << "No sensitive property or parameter found with this 
component ID and name.\n";
   ```



##########
libminifi/src/core/ParameterTokenParser.cpp:
##########
@@ -85,21 +86,31 @@ std::string 
ParameterTokenParser::replaceParameters(ParameterContext* parameter_
     if (!parameter_context) {
       throw ParameterException("Property references a parameter in its value, 
but no parameter context was provided.");
     }
-
     gsl_Assert(token->getName().has_value());
     auto parameter = parameter_context->getParameter(token->getName().value());
     if (!parameter.has_value()) {
       throw ParameterException("Parameter '" + token->getName().value() + "' 
not found");
     }
-    if (is_sensitive) {
-      throw ParameterException("Non-sensitive parameter '" + parameter->name + 
"' cannot be referenced in a sensitive property");
-    }
-    result.append(std::string(token->getAdditionalHashmarks(), '#') + 
parameter->value);
+    result.append(std::string(token->getAdditionalHashmarks(), '#') + 
getRawParameterValue(*parameter));
     last_end = token->getStart() + token->getSize();
   }
   result.append(input_.substr(last_end));
   return result;
 }
 
+std::string NonSensitiveParameterTokenParser::getRawParameterValue(const 
Parameter& parameter) const {
+  if (parameter.sensitive) {
+    throw ParameterException("Sensitive parameter '" + parameter.name + "' 
cannot be referenced in a non-sensitive property");
+  }
+  return parameter.value;
+}
+
+std::string SensitiveParameterTokenParser::getRawParameterValue(const 
Parameter& parameter) const {
+  if (!parameter.sensitive) {
+    throw ParameterException("Non-sensitive parameter '" + parameter.name + "' 
cannot be referenced in a sensitive property");
+  }
+  return utils::crypto::property_encryption::decrypt(parameter.value, 
sensitive_values_encryptor_);
+}

Review Comment:
   Why can't we reference a non-sensitive parameter from a sensitive property? 
(Or even the other way round?)



##########
libminifi/include/core/ParameterTokenParser.h:
##########
@@ -131,5 +138,27 @@ class ParameterTokenParser {
   std::vector<std::unique_ptr<ParameterToken>> tokens_;
 };
 
+class NonSensitiveParameterTokenParser : public ParameterTokenParser {
+ public:
+  using ParameterTokenParser::ParameterTokenParser;
+
+ protected:
+  std::string getRawParameterValue(const Parameter& parameter) const override;
+};
+
+class SensitiveParameterTokenParser : public ParameterTokenParser {
+ public:
+  SensitiveParameterTokenParser(std::string input, 
utils::crypto::EncryptionProvider& sensitive_values_encryptor)
+      : ParameterTokenParser(std::move(input)),
+        sensitive_values_encryptor_(sensitive_values_encryptor) {
+  }
+
+ protected:
+  std::string getRawParameterValue(const Parameter& parameter) const override;
+
+ private:
+  utils::crypto::EncryptionProvider& sensitive_values_encryptor_;

Review Comment:
   could this be a `const &` (also in the constructor)?



##########
libminifi/src/core/json/JsonFlowSerializer.cpp:
##########
@@ -68,18 +68,42 @@ void 
JsonFlowSerializer::encryptSensitiveProperties(rapidjson::Value& property_j
   }
 }
 
-std::string JsonFlowSerializer::serialize(const core::ProcessGroup& 
process_group, const core::flow::FlowSchema& schema, const 
utils::crypto::EncryptionProvider& encryption_provider,
-    const std::unordered_map<utils::Identifier, core::flow::Overrides>& 
overrides) const {
-  gsl_Expects(schema.root_group.size() == 1 && schema.identifier.size() == 1 &&
-      schema.processors.size() == 1 && schema.processor_properties.size() == 1 
&&
-      schema.controller_services.size() == 1 && 
schema.controller_service_properties.size() == 1);
+void JsonFlowSerializer::encryptSensitiveParameters(rapidjson::Value& 
flow_definition_json, rapidjson::Document::AllocatorType& alloc, const 
core::flow::FlowSchema& schema,
+    const utils::crypto::EncryptionProvider& encryption_provider, const 
std::unordered_map<utils::Identifier, core::flow::Overrides>& overrides) const {
+  if (!flow_definition_json.HasMember(schema.parameter_contexts[0])) {
+    return;
+  }
 
-  rapidjson::Document doc;
-  auto alloc = doc.GetAllocator();
-  rapidjson::Value flow_definition_json;
-  flow_definition_json.CopyFrom(flow_definition_json_, alloc);
-  auto& root_group = getMember(flow_definition_json, schema.root_group[0]);
+  auto parameter_contexts = getMember(flow_definition_json, 
schema.parameter_contexts[0]).GetArray();
+  for (auto& parameter_context : parameter_contexts) {
+    for (auto& parameter : getMember(parameter_context, 
schema.parameters[0]).GetArray()) {
+      bool is_sensitive = getMember(parameter, schema.sensitive[0]).GetBool();
+      if (!is_sensitive) {
+        continue;
+      }
+      const std::string parameter_context_id_str{getMember(parameter_context, 
schema.identifier[0]).GetString(), getMember(parameter_context, 
schema.identifier[0]).GetStringLength()};
+      const auto parameter_context_id = 
utils::Identifier::parse(parameter_context_id_str);
+      if (!parameter_context_id) {
+        logger_->log_warn("Invalid parameter context ID found in the flow 
definition: {}", parameter_context_id_str);
+        continue;
+      }
+
+      std::string parameter_value{getMember(parameter, 
schema.value[0]).GetString(), getMember(parameter, 
schema.value[0]).GetStringLength()};
+      if (overrides.contains(*parameter_context_id)) {
+        const auto& override_values = overrides.at(*parameter_context_id);
+        const auto parameter_name = getMember(parameter, 
schema.name[0]).GetString();

Review Comment:
   we should use `GetStringLength()` here, too, as elsewhere (eg. at line 91 
above)



##########
extensions/standard-processors/tests/unit/FlowJsonTests.cpp:
##########
@@ -789,4 +808,118 @@ TEST_CASE("Dynamic properties can use parameters") {
   CHECK(value == "value1");
 }
 
+TEST_CASE("Test sensitive parameters in sensitive properties") {
+  ConfigurationTestController test_controller;
+  auto context = test_controller.getContext();
+  auto encrypted_parameter_value = 
minifi::utils::crypto::property_encryption::encrypt("value1", 
*context.sensitive_values_encryptor);
+  auto encrypted_sensitive_property_value = 
minifi::utils::crypto::property_encryption::encrypt("#{my_value}", 
*context.sensitive_values_encryptor);
+  core::flow::AdaptiveConfiguration config(context);
+
+  static const std::string CONFIG_JSON =
+      fmt::format(R"(
+{{
+  "parameterContexts": [
+    {{
+      "identifier": "721e10b7-8e00-3188-9a27-476cca376978",
+      "name": "my-context",
+      "description": "my parameter context",
+      "parameters": [
+        {{
+          "name": "my_value",
+          "description": "",
+          "sensitive": true,
+          "value": "{}"
+        }}
+      ]
+    }}
+  ],
+  "rootGroup": {{
+    "name": "MiNiFi Flow",
+    "processors": [{{
+      "identifier": "00000000-0000-0000-0000-000000000001",
+      "name": "MyProcessor",
+      "type": "org.apache.nifi.processors.DummyFlowJsonProcessor",
+      "schedulingStrategy": "TIMER_DRIVEN",
+      "schedulingPeriod": "3 sec",
+      "properties": {{
+        "Sensitive Property": "{}"
+      }}
+    }}],
+    "parameterContextName": "my-context"

Review Comment:
   Is "parameterContextName" something we inherited from NiFi? Since the 
parameter context has a unique ID, `"parameterContext": 
"721e10b7-8e00-3188-9a27-476cca376978"` seems better, and more in line with our 
existing schema.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to