exceptionfactory commented on code in PR #6054:
URL: https://github.com/apache/nifi/pull/6054#discussion_r876334681
##########
nifi-commons/nifi-flow-encryptor/pom.xml:
##########
@@ -34,5 +34,39 @@
</exclusion>
</exclusions>
</dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ <version>2.13.1</version>
Review Comment:
The version is managed through `jackson-bom` in the root Maven
configuration, so this can be removed.
```suggestion
```
##########
nifi-commons/nifi-flow-encryptor/pom.xml:
##########
@@ -34,5 +34,39 @@
</exclusion>
</exclusions>
</dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ <version>2.13.1</version>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ <version>2.13.1</version>
+ </dependency>
Review Comment:
This dependency is duplicated and can be removed.
```suggestion
```
##########
nifi-commons/nifi-flow-encryptor/src/main/java/org/apache/nifi/flow/encryptor/JsonFlowEncryptor.java:
##########
@@ -0,0 +1,118 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.flow.encryptor;
+
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.nifi.encrypt.PropertyEncryptor;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UncheckedIOException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class JsonFlowEncryptor implements FlowEncryptor {
+ private static final Pattern ENCRYPTED_PATTERN =
Pattern.compile("enc\\{([^\\}]+?)\\}");
+
+ private static final int FIRST_GROUP = 1;
+
+ private static final String ENCRYPTED_FORMAT = "enc{%s}";
+
+
+ @Override
+ public void processFlow(InputStream inputStream, OutputStream
outputStream, PropertyEncryptor inputEncryptor, PropertyEncryptor
outputEncryptor) {
Review Comment:
Input variables should be declared `final`.
```suggestion
public void processFlow(final InputStream inputStream, final
OutputStream outputStream, final PropertyEncryptor inputEncryptor, final
PropertyEncryptor outputEncryptor) {
```
##########
nifi-commons/nifi-flow-encryptor/pom.xml:
##########
@@ -34,5 +34,39 @@
</exclusion>
</exclusions>
</dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ <version>2.13.1</version>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ <version>2.13.1</version>
+ </dependency>
+ <dependency>
+ <groupId>org.xmlunit</groupId>
+ <artifactId>xmlunit-core</artifactId>
+ <version>2.9.0</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.xmlunit</groupId>
+ <artifactId>xmlunit-matchers</artifactId>
+ <version>2.9.0</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.xmlunit</groupId>
+ <artifactId>xmlunit-assertj3</artifactId>
+ <version>2.9.0</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-xml-processing</artifactId>
+ <version>1.17.0-SNAPSHOT</version>
+ <scope>compile</scope>
+ </dependency>
Review Comment:
Recommend moving standard dependencies before test dependencies.
##########
nifi-commons/nifi-flow-encryptor/src/main/java/org/apache/nifi/flow/encryptor/JsonFlowEncryptor.java:
##########
@@ -0,0 +1,118 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.flow.encryptor;
+
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.nifi.encrypt.PropertyEncryptor;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UncheckedIOException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class JsonFlowEncryptor implements FlowEncryptor {
+ private static final Pattern ENCRYPTED_PATTERN =
Pattern.compile("enc\\{([^\\}]+?)\\}");
+
+ private static final int FIRST_GROUP = 1;
+
+ private static final String ENCRYPTED_FORMAT = "enc{%s}";
+
+
+ @Override
+ public void processFlow(InputStream inputStream, OutputStream
outputStream, PropertyEncryptor inputEncryptor, PropertyEncryptor
outputEncryptor) {
+ final JsonFactory factory = new JsonFactory();
+ try (final JsonGenerator generator =
factory.createGenerator(outputStream)){
+ try (final JsonParser parser = factory.createParser(inputStream)) {
+ parser.setCodec(new ObjectMapper());
+ processJsonByTokens(parser, generator, inputEncryptor,
outputEncryptor);
+ }
+ } catch (IOException e) {
+ throw new UncheckedIOException("Failed Processing Flow
Configuration", e);
+ }
+ }
+
+ private void processJsonByTokens(final JsonParser parser, final
JsonGenerator generator,
+ final PropertyEncryptor inputEncryptor,
final PropertyEncryptor outputEncryptor) throws IOException {
+ while (true) {
+ final JsonToken token = parser.nextToken();
+ if (token == null) {
+ return;
+ }
+
+ switch (token) {
+ case NOT_AVAILABLE:
+ break;
+ case START_OBJECT:
+ generator.writeStartObject();
+ break;
+ case END_OBJECT:
+ generator.writeEndObject();
+ break;
+ case START_ARRAY:
+ generator.writeStartArray();
+ break;
+ case END_ARRAY:
+ generator.writeEndArray();
+ break;
+ case FIELD_NAME:
+ generator.writeFieldName(parser.getValueAsString());
+ break;
+ case VALUE_EMBEDDED_OBJECT:
+ generator.writeEmbeddedObject(parser.getEmbeddedObject());
+ break;
+ case VALUE_STRING:
+ final String value = parser.getValueAsString();
+ final Matcher matcher = ENCRYPTED_PATTERN.matcher(value);
+ if (matcher.matches()) {
+
generator.writeString(getOutputEncrypted(matcher.group(FIRST_GROUP),
inputEncryptor, outputEncryptor));
+ } else {
+ generator.writeString(value);
+ }
+ break;
+ case VALUE_NUMBER_INT:
+ generator.writeNumber(parser.getIntValue());
+ break;
+ case VALUE_NUMBER_FLOAT:
+ generator.writeNumber(parser.getFloatValue());
+ break;
+ case VALUE_TRUE:
+ generator.writeBoolean(true);
+ break;
+ case VALUE_FALSE:
+ generator.writeBoolean(false);
+ break;
+ case VALUE_NULL:
+ generator.writeNull();
+ break;
+ default:
+ break;
Review Comment:
It seems better to through an `IllegalStateException()` here to ensure the
method covers all JSON token types.
##########
nifi-commons/nifi-flow-encryptor/src/main/java/org/apache/nifi/flow/encryptor/StandardFlowEncryptor.java:
##########
@@ -49,38 +42,27 @@ public class StandardFlowEncryptor implements FlowEncryptor
{
*/
@Override
public void processFlow(final InputStream inputStream, final OutputStream
outputStream, final PropertyEncryptor inputEncryptor, final PropertyEncryptor
outputEncryptor) {
- try (final PrintWriter writer = new PrintWriter(new
OutputStreamWriter(outputStream))) {
- try (final BufferedReader reader = new BufferedReader(new
InputStreamReader(inputStream))) {
- reader.lines().forEach(line -> {
- final Matcher matcher = ENCRYPTED_PATTERN.matcher(line);
-
- final StringBuffer sb = new StringBuffer();
- boolean matched = false;
- while (matcher.find()) {
- final String outputEncrypted =
getOutputEncrypted(matcher.group(FIRST_GROUP), inputEncryptor, outputEncryptor);
- matcher.appendReplacement(sb, outputEncrypted);
- matched = true;
- }
-
- final String outputLine;
- if (matched) {
- matcher.appendTail(sb);
- outputLine = sb.toString();
- } else {
- outputLine = line;
- }
-
- writer.println(outputLine);
- });
+ final BufferedInputStream bufferedInputStream = new
BufferedInputStream(inputStream);
+ if (bufferedInputStream.markSupported()) {
Review Comment:
This conditional is unnecessary since `BufferedInputStream` supports marking.
##########
nifi-commons/nifi-flow-encryptor/src/main/java/org/apache/nifi/flow/encryptor/XmlFlowEncryptor.java:
##########
@@ -0,0 +1,120 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.flow.encryptor;
+
+import org.apache.nifi.encrypt.PropertyEncryptor;
+import org.apache.nifi.xml.processing.stream.StandardXMLEventReaderProvider;
+import org.apache.nifi.xml.processing.stream.XMLEventReaderProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.xml.stream.XMLEventFactory;
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.XMLEventWriter;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.events.Characters;
+import javax.xml.stream.events.StartDocument;
+import javax.xml.stream.events.XMLEvent;
+import javax.xml.transform.stream.StreamSource;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.UncheckedIOException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class XmlFlowEncryptor implements FlowEncryptor {
+ private static final Pattern ENCRYPTED_PATTERN =
Pattern.compile("enc\\{([^\\}]+?)\\}");
+
+ private static final int FIRST_GROUP = 1;
+
+ private static final String ENCRYPTED_FORMAT = "enc{%s}";
+
+ private static final XMLEventReaderProvider eventReaderProvider = new
StandardXMLEventReaderProvider();
+
+ private final Logger logger =
LoggerFactory.getLogger(XmlFlowEncryptor.class);
+
+ @Override
+ public void processFlow(InputStream inputStream, OutputStream
outputStream, PropertyEncryptor inputEncryptor, PropertyEncryptor
outputEncryptor) {
+ XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance();
Review Comment:
This `XMLInputFactory` declaration is not necessary with the use of the
`XMLEventReaderProvider`.
```suggestion
```
##########
nifi-commons/nifi-flow-encryptor/src/main/java/org/apache/nifi/flow/encryptor/XmlFlowEncryptor.java:
##########
@@ -0,0 +1,120 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.flow.encryptor;
+
+import org.apache.nifi.encrypt.PropertyEncryptor;
+import org.apache.nifi.xml.processing.stream.StandardXMLEventReaderProvider;
+import org.apache.nifi.xml.processing.stream.XMLEventReaderProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.xml.stream.XMLEventFactory;
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.XMLEventWriter;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.events.Characters;
+import javax.xml.stream.events.StartDocument;
+import javax.xml.stream.events.XMLEvent;
+import javax.xml.transform.stream.StreamSource;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.UncheckedIOException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class XmlFlowEncryptor implements FlowEncryptor {
+ private static final Pattern ENCRYPTED_PATTERN =
Pattern.compile("enc\\{([^\\}]+?)\\}");
+
+ private static final int FIRST_GROUP = 1;
+
+ private static final String ENCRYPTED_FORMAT = "enc{%s}";
+
+ private static final XMLEventReaderProvider eventReaderProvider = new
StandardXMLEventReaderProvider();
+
+ private final Logger logger =
LoggerFactory.getLogger(XmlFlowEncryptor.class);
+
+ @Override
+ public void processFlow(InputStream inputStream, OutputStream
outputStream, PropertyEncryptor inputEncryptor, PropertyEncryptor
outputEncryptor) {
+ XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance();
+ XMLOutputFactory xmlOutputFactory = XMLOutputFactory.newInstance();
+ final XMLEventFactory eventFactory = XMLEventFactory.newInstance();
+ try {
+ final XMLEventReader reader =
eventReaderProvider.getEventReader(new StreamSource(inputStream));
+ final XMLEventWriter writer =
xmlOutputFactory.createXMLEventWriter(outputStream, "UTF-8");
Review Comment:
Recommend replacing the `UTF-8` string with `StandardCharsets.UTF_8.name()`
##########
nifi-commons/nifi-flow-encryptor/src/main/java/org/apache/nifi/flow/encryptor/XmlFlowEncryptor.java:
##########
@@ -0,0 +1,120 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.flow.encryptor;
+
+import org.apache.nifi.encrypt.PropertyEncryptor;
+import org.apache.nifi.xml.processing.stream.StandardXMLEventReaderProvider;
+import org.apache.nifi.xml.processing.stream.XMLEventReaderProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.xml.stream.XMLEventFactory;
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.XMLEventWriter;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.events.Characters;
+import javax.xml.stream.events.StartDocument;
+import javax.xml.stream.events.XMLEvent;
+import javax.xml.transform.stream.StreamSource;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.UncheckedIOException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class XmlFlowEncryptor implements FlowEncryptor {
+ private static final Pattern ENCRYPTED_PATTERN =
Pattern.compile("enc\\{([^\\}]+?)\\}");
+
+ private static final int FIRST_GROUP = 1;
+
+ private static final String ENCRYPTED_FORMAT = "enc{%s}";
+
+ private static final XMLEventReaderProvider eventReaderProvider = new
StandardXMLEventReaderProvider();
+
+ private final Logger logger =
LoggerFactory.getLogger(XmlFlowEncryptor.class);
+
+ @Override
+ public void processFlow(InputStream inputStream, OutputStream
outputStream, PropertyEncryptor inputEncryptor, PropertyEncryptor
outputEncryptor) {
+ XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance();
+ XMLOutputFactory xmlOutputFactory = XMLOutputFactory.newInstance();
+ final XMLEventFactory eventFactory = XMLEventFactory.newInstance();
+ try {
+ final XMLEventReader reader =
eventReaderProvider.getEventReader(new StreamSource(inputStream));
+ final XMLEventWriter writer =
xmlOutputFactory.createXMLEventWriter(outputStream, "UTF-8");
+ while (reader.hasNext()) {
+ final XMLEvent event = reader.nextEvent();
+ if (event.getEventType() == XMLEvent.START_DOCUMENT) {
+ final StartDocument startDocument = (StartDocument) event;
+ handleXmlStartDocument(startDocument, outputStream);
+ } else if (event.getEventType() == XMLEvent.CHARACTERS) {
+ final Characters characters = event.asCharacters();
+ final String value = characters.getData();
+ final Matcher matcher = ENCRYPTED_PATTERN.matcher(value);
+ if (matcher.matches()) {
+ final String processedValue =
getOutputEncrypted(matcher.group(FIRST_GROUP), inputEncryptor, outputEncryptor);
+
writer.add(eventFactory.createCharacters(processedValue));
+ } else {
+ writer.add(characters);
+ }
+ } else if (event.getEventType() == XMLEvent.COMMENT) {
+ writer.add(event);
+ final Characters newLine =
eventFactory.createCharacters("\n");
+ writer.add(newLine);
+ } else if (event.getEventType() == XMLEvent.END_DOCUMENT) {
+ final Characters newLine =
eventFactory.createCharacters("\n");
+ writer.add(newLine);
+ } else {
+ writer.add(event);
+ }
+ }
+ writer.flush();
+ writer.close();
+ reader.close();
+ outputStream.close();
+ inputStream.close();
+ } catch (XMLStreamException e) {
+ logger.error("Failed Processing Flow Configuration:
XMLStreamException occurred");
+ e.printStackTrace();
+ } catch (IOException e) {
+ throw new UncheckedIOException("Failed Processing Flow
Configuration", e);
+ }
+ }
+
+ private String getOutputEncrypted(final String inputEncrypted, final
PropertyEncryptor inputEncryptor, final PropertyEncryptor outputEncryptor) {
+ final String inputDecrypted = inputEncryptor.decrypt(inputEncrypted);
+ final String outputEncrypted = outputEncryptor.encrypt(inputDecrypted);
+ return String.format(ENCRYPTED_FORMAT, outputEncrypted);
+ }
+
+ private void handleXmlStartDocument(final StartDocument startDocument,
final OutputStream outputStream) {
+ final PrintWriter writer = new PrintWriter(new
OutputStreamWriter(outputStream));
+ final String version = startDocument.getVersion();
+ final String encoding = startDocument.getCharacterEncodingScheme();
+ String standalone = "";
+ if (startDocument.standaloneSet()) {
+ standalone = startDocument.isStandalone() ? " standalone=\"yes\""
: " standalone=\"no\"";
+ }
+ final String line = String.format("<?xml version=\"%s\"
encoding=\"%s\"%s?>", version, encoding, standalone);
+ writer.println(line);
+ writer.flush();
+ }
Review Comment:
Does XMLEventWriter not handle the `standalone` property on the
`StartDocument` event? The default value is `no`, which is what all flow.xml
documents should have. It seems better to use just use the XMLEventWriter
behavior instead of writing a custom string.
##########
nifi-commons/nifi-flow-encryptor/src/main/java/org/apache/nifi/flow/encryptor/XmlFlowEncryptor.java:
##########
@@ -0,0 +1,120 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.flow.encryptor;
+
+import org.apache.nifi.encrypt.PropertyEncryptor;
+import org.apache.nifi.xml.processing.stream.StandardXMLEventReaderProvider;
+import org.apache.nifi.xml.processing.stream.XMLEventReaderProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.xml.stream.XMLEventFactory;
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.XMLEventWriter;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.events.Characters;
+import javax.xml.stream.events.StartDocument;
+import javax.xml.stream.events.XMLEvent;
+import javax.xml.transform.stream.StreamSource;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.UncheckedIOException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class XmlFlowEncryptor implements FlowEncryptor {
+ private static final Pattern ENCRYPTED_PATTERN =
Pattern.compile("enc\\{([^\\}]+?)\\}");
+
+ private static final int FIRST_GROUP = 1;
+
+ private static final String ENCRYPTED_FORMAT = "enc{%s}";
+
+ private static final XMLEventReaderProvider eventReaderProvider = new
StandardXMLEventReaderProvider();
+
+ private final Logger logger =
LoggerFactory.getLogger(XmlFlowEncryptor.class);
+
+ @Override
+ public void processFlow(InputStream inputStream, OutputStream
outputStream, PropertyEncryptor inputEncryptor, PropertyEncryptor
outputEncryptor) {
+ XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance();
+ XMLOutputFactory xmlOutputFactory = XMLOutputFactory.newInstance();
+ final XMLEventFactory eventFactory = XMLEventFactory.newInstance();
+ try {
+ final XMLEventReader reader =
eventReaderProvider.getEventReader(new StreamSource(inputStream));
+ final XMLEventWriter writer =
xmlOutputFactory.createXMLEventWriter(outputStream, "UTF-8");
+ while (reader.hasNext()) {
+ final XMLEvent event = reader.nextEvent();
+ if (event.getEventType() == XMLEvent.START_DOCUMENT) {
+ final StartDocument startDocument = (StartDocument) event;
+ handleXmlStartDocument(startDocument, outputStream);
+ } else if (event.getEventType() == XMLEvent.CHARACTERS) {
+ final Characters characters = event.asCharacters();
+ final String value = characters.getData();
+ final Matcher matcher = ENCRYPTED_PATTERN.matcher(value);
+ if (matcher.matches()) {
+ final String processedValue =
getOutputEncrypted(matcher.group(FIRST_GROUP), inputEncryptor, outputEncryptor);
+
writer.add(eventFactory.createCharacters(processedValue));
+ } else {
+ writer.add(characters);
+ }
+ } else if (event.getEventType() == XMLEvent.COMMENT) {
+ writer.add(event);
+ final Characters newLine =
eventFactory.createCharacters("\n");
+ writer.add(newLine);
+ } else if (event.getEventType() == XMLEvent.END_DOCUMENT) {
+ final Characters newLine =
eventFactory.createCharacters("\n");
+ writer.add(newLine);
+ } else {
+ writer.add(event);
+ }
+ }
+ writer.flush();
+ writer.close();
+ reader.close();
+ outputStream.close();
+ inputStream.close();
Review Comment:
Instead of all these `close()` methods, the Readers and Writers should be
wrapped in a try-with-resources block.
##########
nifi-commons/nifi-flow-encryptor/src/main/java/org/apache/nifi/flow/encryptor/XmlFlowEncryptor.java:
##########
@@ -0,0 +1,120 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.flow.encryptor;
+
+import org.apache.nifi.encrypt.PropertyEncryptor;
+import org.apache.nifi.xml.processing.stream.StandardXMLEventReaderProvider;
+import org.apache.nifi.xml.processing.stream.XMLEventReaderProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.xml.stream.XMLEventFactory;
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.XMLEventWriter;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.events.Characters;
+import javax.xml.stream.events.StartDocument;
+import javax.xml.stream.events.XMLEvent;
+import javax.xml.transform.stream.StreamSource;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.UncheckedIOException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class XmlFlowEncryptor implements FlowEncryptor {
+ private static final Pattern ENCRYPTED_PATTERN =
Pattern.compile("enc\\{([^\\}]+?)\\}");
+
+ private static final int FIRST_GROUP = 1;
+
+ private static final String ENCRYPTED_FORMAT = "enc{%s}";
+
+ private static final XMLEventReaderProvider eventReaderProvider = new
StandardXMLEventReaderProvider();
+
+ private final Logger logger =
LoggerFactory.getLogger(XmlFlowEncryptor.class);
+
+ @Override
+ public void processFlow(InputStream inputStream, OutputStream
outputStream, PropertyEncryptor inputEncryptor, PropertyEncryptor
outputEncryptor) {
+ XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance();
+ XMLOutputFactory xmlOutputFactory = XMLOutputFactory.newInstance();
+ final XMLEventFactory eventFactory = XMLEventFactory.newInstance();
+ try {
+ final XMLEventReader reader =
eventReaderProvider.getEventReader(new StreamSource(inputStream));
+ final XMLEventWriter writer =
xmlOutputFactory.createXMLEventWriter(outputStream, "UTF-8");
+ while (reader.hasNext()) {
+ final XMLEvent event = reader.nextEvent();
+ if (event.getEventType() == XMLEvent.START_DOCUMENT) {
+ final StartDocument startDocument = (StartDocument) event;
+ handleXmlStartDocument(startDocument, outputStream);
+ } else if (event.getEventType() == XMLEvent.CHARACTERS) {
+ final Characters characters = event.asCharacters();
+ final String value = characters.getData();
+ final Matcher matcher = ENCRYPTED_PATTERN.matcher(value);
+ if (matcher.matches()) {
+ final String processedValue =
getOutputEncrypted(matcher.group(FIRST_GROUP), inputEncryptor, outputEncryptor);
+
writer.add(eventFactory.createCharacters(processedValue));
+ } else {
+ writer.add(characters);
+ }
+ } else if (event.getEventType() == XMLEvent.COMMENT) {
+ writer.add(event);
+ final Characters newLine =
eventFactory.createCharacters("\n");
+ writer.add(newLine);
+ } else if (event.getEventType() == XMLEvent.END_DOCUMENT) {
+ final Characters newLine =
eventFactory.createCharacters("\n");
+ writer.add(newLine);
+ } else {
+ writer.add(event);
+ }
+ }
+ writer.flush();
+ writer.close();
+ reader.close();
+ outputStream.close();
+ inputStream.close();
+ } catch (XMLStreamException e) {
+ logger.error("Failed Processing Flow Configuration:
XMLStreamException occurred");
+ e.printStackTrace();
Review Comment:
The `printStackTrace()` call should be removed, as well as the error log,
and a `RuntimeException` should be thrown that includes a message and the
`XMLStreamException`.
##########
nifi-commons/nifi-flow-encryptor/src/main/java/org/apache/nifi/flow/encryptor/StandardFlowEncryptor.java:
##########
@@ -49,38 +42,27 @@ public class StandardFlowEncryptor implements FlowEncryptor
{
*/
@Override
public void processFlow(final InputStream inputStream, final OutputStream
outputStream, final PropertyEncryptor inputEncryptor, final PropertyEncryptor
outputEncryptor) {
- try (final PrintWriter writer = new PrintWriter(new
OutputStreamWriter(outputStream))) {
- try (final BufferedReader reader = new BufferedReader(new
InputStreamReader(inputStream))) {
- reader.lines().forEach(line -> {
- final Matcher matcher = ENCRYPTED_PATTERN.matcher(line);
-
- final StringBuffer sb = new StringBuffer();
- boolean matched = false;
- while (matcher.find()) {
- final String outputEncrypted =
getOutputEncrypted(matcher.group(FIRST_GROUP), inputEncryptor, outputEncryptor);
- matcher.appendReplacement(sb, outputEncrypted);
- matched = true;
- }
-
- final String outputLine;
- if (matched) {
- matcher.appendTail(sb);
- outputLine = sb.toString();
- } else {
- outputLine = line;
- }
-
- writer.println(outputLine);
- });
+ final BufferedInputStream bufferedInputStream = new
BufferedInputStream(inputStream);
+ if (bufferedInputStream.markSupported()) {
+ final byte[] firstByte = new byte[1];
+ bufferedInputStream.mark(1);
+ try {
+ bufferedInputStream.read(firstByte, 0, 1);
Review Comment:
This could be changed to use the simple `read()` method:
```suggestion
bufferedInputStream.mark(1);
try {
final int firstByte = bufferedInputStream.read();
```
##########
nifi-commons/nifi-flow-encryptor/src/main/java/org/apache/nifi/flow/encryptor/StandardFlowEncryptor.java:
##########
@@ -49,38 +42,27 @@ public class StandardFlowEncryptor implements FlowEncryptor
{
*/
@Override
public void processFlow(final InputStream inputStream, final OutputStream
outputStream, final PropertyEncryptor inputEncryptor, final PropertyEncryptor
outputEncryptor) {
- try (final PrintWriter writer = new PrintWriter(new
OutputStreamWriter(outputStream))) {
- try (final BufferedReader reader = new BufferedReader(new
InputStreamReader(inputStream))) {
- reader.lines().forEach(line -> {
- final Matcher matcher = ENCRYPTED_PATTERN.matcher(line);
-
- final StringBuffer sb = new StringBuffer();
- boolean matched = false;
- while (matcher.find()) {
- final String outputEncrypted =
getOutputEncrypted(matcher.group(FIRST_GROUP), inputEncryptor, outputEncryptor);
- matcher.appendReplacement(sb, outputEncrypted);
- matched = true;
- }
-
- final String outputLine;
- if (matched) {
- matcher.appendTail(sb);
- outputLine = sb.toString();
- } else {
- outputLine = line;
- }
-
- writer.println(outputLine);
- });
+ final BufferedInputStream bufferedInputStream = new
BufferedInputStream(inputStream);
+ if (bufferedInputStream.markSupported()) {
+ final byte[] firstByte = new byte[1];
+ bufferedInputStream.mark(1);
+ try {
+ bufferedInputStream.read(firstByte, 0, 1);
+ bufferedInputStream.reset();
+ // Flow must be XML or JSON
+ if (firstByte[0] == '<') {
+ final XmlFlowEncryptor flowEncryptor = new
XmlFlowEncryptor();
+ flowEncryptor.processFlow(bufferedInputStream,
outputStream, inputEncryptor, outputEncryptor);
+ } else {
+ final JsonFlowEncryptor flowEncryptor = new
JsonFlowEncryptor();
+ flowEncryptor.processFlow(bufferedInputStream,
outputStream, inputEncryptor, outputEncryptor);
+ }
+ } catch (IOException e) {
+ logger.error(e.getMessage());
+ e.printStackTrace();
Review Comment:
This `printStackTrace()` call should be removed.
```suggestion
```
##########
nifi-commons/nifi-flow-encryptor/src/main/java/org/apache/nifi/flow/encryptor/JsonFlowEncryptor.java:
##########
@@ -0,0 +1,118 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.flow.encryptor;
+
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.nifi.encrypt.PropertyEncryptor;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UncheckedIOException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class JsonFlowEncryptor implements FlowEncryptor {
+ private static final Pattern ENCRYPTED_PATTERN =
Pattern.compile("enc\\{([^\\}]+?)\\}");
+
+ private static final int FIRST_GROUP = 1;
+
+ private static final String ENCRYPTED_FORMAT = "enc{%s}";
+
+
+ @Override
+ public void processFlow(InputStream inputStream, OutputStream
outputStream, PropertyEncryptor inputEncryptor, PropertyEncryptor
outputEncryptor) {
+ final JsonFactory factory = new JsonFactory();
+ try (final JsonGenerator generator =
factory.createGenerator(outputStream)){
+ try (final JsonParser parser = factory.createParser(inputStream)) {
+ parser.setCodec(new ObjectMapper());
+ processJsonByTokens(parser, generator, inputEncryptor,
outputEncryptor);
+ }
+ } catch (IOException e) {
+ throw new UncheckedIOException("Failed Processing Flow
Configuration", e);
+ }
+ }
+
+ private void processJsonByTokens(final JsonParser parser, final
JsonGenerator generator,
+ final PropertyEncryptor inputEncryptor,
final PropertyEncryptor outputEncryptor) throws IOException {
+ while (true) {
+ final JsonToken token = parser.nextToken();
+ if (token == null) {
+ return;
+ }
Review Comment:
Instead of having `while (true)`, what about having the loop while
`JsonToken` is not null?
##########
nifi-commons/nifi-flow-encryptor/src/main/java/org/apache/nifi/flow/encryptor/JsonFlowEncryptor.java:
##########
@@ -0,0 +1,118 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.flow.encryptor;
+
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.nifi.encrypt.PropertyEncryptor;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UncheckedIOException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class JsonFlowEncryptor implements FlowEncryptor {
+ private static final Pattern ENCRYPTED_PATTERN =
Pattern.compile("enc\\{([^\\}]+?)\\}");
+
+ private static final int FIRST_GROUP = 1;
+
+ private static final String ENCRYPTED_FORMAT = "enc{%s}";
Review Comment:
It looks like variables and the associated `getOutputEncrypted()` could be
refactored to a common abstract class which both the JSON and XML
implementations could extend.
##########
nifi-commons/nifi-flow-encryptor/src/main/java/org/apache/nifi/flow/encryptor/JsonFlowEncryptor.java:
##########
@@ -0,0 +1,118 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.flow.encryptor;
+
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.nifi.encrypt.PropertyEncryptor;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UncheckedIOException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class JsonFlowEncryptor implements FlowEncryptor {
+ private static final Pattern ENCRYPTED_PATTERN =
Pattern.compile("enc\\{([^\\}]+?)\\}");
Review Comment:
Recommend adjusting the pattern to include start and end boundaries:
```suggestion
private static final Pattern ENCRYPTED_PATTERN =
Pattern.compile("^enc\\{([^\\}]+?)\\}$");
```
##########
nifi-commons/nifi-flow-encryptor/src/main/java/org/apache/nifi/flow/encryptor/XmlFlowEncryptor.java:
##########
@@ -0,0 +1,120 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.flow.encryptor;
+
+import org.apache.nifi.encrypt.PropertyEncryptor;
+import org.apache.nifi.xml.processing.stream.StandardXMLEventReaderProvider;
+import org.apache.nifi.xml.processing.stream.XMLEventReaderProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.xml.stream.XMLEventFactory;
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.XMLEventWriter;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.events.Characters;
+import javax.xml.stream.events.StartDocument;
+import javax.xml.stream.events.XMLEvent;
+import javax.xml.transform.stream.StreamSource;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.UncheckedIOException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class XmlFlowEncryptor implements FlowEncryptor {
+ private static final Pattern ENCRYPTED_PATTERN =
Pattern.compile("enc\\{([^\\}]+?)\\}");
+
+ private static final int FIRST_GROUP = 1;
+
+ private static final String ENCRYPTED_FORMAT = "enc{%s}";
+
+ private static final XMLEventReaderProvider eventReaderProvider = new
StandardXMLEventReaderProvider();
+
+ private final Logger logger =
LoggerFactory.getLogger(XmlFlowEncryptor.class);
Review Comment:
The `logger` should be removed and replaced with throwing an exception as
needed.
##########
nifi-commons/nifi-flow-encryptor/src/test/java/org/apache/nifi/flow/encryptor/StandardFlowEncryptorTest.java:
##########
@@ -89,7 +100,145 @@ public void testProcessNoEncrypted() {
assertEquals(property, outputProperty);
}
+ @Test
+ public void testProcessJson() throws JsonProcessingException {
+ final String password = "This is a bad password";
+ final String encryptedPassword = String.format(ENCRYPTED_FORMAT,
inputEncryptor.encrypt(password));
+
+ final String sampleFlowJson = getSampleFlowJson(encryptedPassword);
+
+ final InputStream inputStream = new
ByteArrayInputStream(sampleFlowJson.getBytes(StandardCharsets.UTF_8));
+ final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ flowEncryptor.processFlow(inputStream, outputStream, inputEncryptor,
outputEncryptor);
+
+ final ObjectMapper mapper = new ObjectMapper();
+
+ final String outputFlowJson = new String(outputStream.toByteArray());
+
+ assertNotEquals(mapper.readTree(sampleFlowJson),
mapper.readTree(outputFlowJson));
+
+ final JsonNode outputJsonNode = mapper.readTree(outputFlowJson);
+
+ final String outputEncryptedPassword = outputJsonNode
+ .path("processors")
+ .get(0)
+ .path("properties")
+ .path("password")
+ .asText();
+
+ final Matcher outputMatcher = PATTERN.matcher(outputEncryptedPassword);
+ assertTrue(outputMatcher.matches());
+ assertEquals(password,
outputEncryptor.decrypt(outputMatcher.group(1)));
+ }
+
+ @Test
+ public void testProcessXml() {
+ final String password = "This is a bad password!";
+ final String encryptedPassword = String.format(ENCRYPTED_FORMAT,
inputEncryptor.encrypt(password));
+ final String sampleFlowXml = getSampleFlowXml(encryptedPassword);
+ final InputStream inputStream = new
ByteArrayInputStream(sampleFlowXml.getBytes(StandardCharsets.UTF_8));
+ final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ flowEncryptor.processFlow(inputStream, outputStream, inputEncryptor,
outputEncryptor);
+
+ final String outputXml = new String(outputStream.toByteArray());
+
+ final Source control = Input.fromString(sampleFlowXml).build();
+ final Source test = Input.fromString(outputXml).build();
+ DifferenceEngine diff = new DOMDifferenceEngine();
+ diff.addDifferenceListener((comparison, outcome) -> {
+ final Matcher inputMatcher =
PATTERN.matcher(comparison.getControlDetails().getValue().toString());
+ final Matcher outputMatcher =
PATTERN.matcher(comparison.getTestDetails().getValue().toString());
+ if (inputMatcher.matches() && outputMatcher.matches()) {
+ assertEquals(inputEncryptor.decrypt(inputMatcher.group(1)),
outputEncryptor.decrypt(outputMatcher.group(1)));
+ }
+ });
+ diff.compare(control, test);
Review Comment:
Introducing the XMLUnit dependency seems like a lot just to perform this
comparison. It seems better to avoid the additional dependencies and stay with
a simpler comparison.
##########
nifi-commons/nifi-flow-encryptor/src/main/java/org/apache/nifi/flow/encryptor/XmlFlowEncryptor.java:
##########
@@ -0,0 +1,120 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.flow.encryptor;
+
+import org.apache.nifi.encrypt.PropertyEncryptor;
+import org.apache.nifi.xml.processing.stream.StandardXMLEventReaderProvider;
+import org.apache.nifi.xml.processing.stream.XMLEventReaderProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.xml.stream.XMLEventFactory;
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.XMLEventWriter;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.events.Characters;
+import javax.xml.stream.events.StartDocument;
+import javax.xml.stream.events.XMLEvent;
+import javax.xml.transform.stream.StreamSource;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.UncheckedIOException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class XmlFlowEncryptor implements FlowEncryptor {
+ private static final Pattern ENCRYPTED_PATTERN =
Pattern.compile("enc\\{([^\\}]+?)\\}");
+
+ private static final int FIRST_GROUP = 1;
+
+ private static final String ENCRYPTED_FORMAT = "enc{%s}";
+
+ private static final XMLEventReaderProvider eventReaderProvider = new
StandardXMLEventReaderProvider();
+
+ private final Logger logger =
LoggerFactory.getLogger(XmlFlowEncryptor.class);
+
+ @Override
+ public void processFlow(InputStream inputStream, OutputStream
outputStream, PropertyEncryptor inputEncryptor, PropertyEncryptor
outputEncryptor) {
+ XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance();
+ XMLOutputFactory xmlOutputFactory = XMLOutputFactory.newInstance();
+ final XMLEventFactory eventFactory = XMLEventFactory.newInstance();
+ try {
+ final XMLEventReader reader =
eventReaderProvider.getEventReader(new StreamSource(inputStream));
+ final XMLEventWriter writer =
xmlOutputFactory.createXMLEventWriter(outputStream, "UTF-8");
+ while (reader.hasNext()) {
+ final XMLEvent event = reader.nextEvent();
+ if (event.getEventType() == XMLEvent.START_DOCUMENT) {
+ final StartDocument startDocument = (StartDocument) event;
+ handleXmlStartDocument(startDocument, outputStream);
+ } else if (event.getEventType() == XMLEvent.CHARACTERS) {
+ final Characters characters = event.asCharacters();
+ final String value = characters.getData();
+ final Matcher matcher = ENCRYPTED_PATTERN.matcher(value);
+ if (matcher.matches()) {
+ final String processedValue =
getOutputEncrypted(matcher.group(FIRST_GROUP), inputEncryptor, outputEncryptor);
+
writer.add(eventFactory.createCharacters(processedValue));
+ } else {
+ writer.add(characters);
+ }
+ } else if (event.getEventType() == XMLEvent.COMMENT) {
+ writer.add(event);
+ final Characters newLine =
eventFactory.createCharacters("\n");
+ writer.add(newLine);
Review Comment:
Is there a particular reason for adding the newline? It seems best to avoid
adding the newline character.
##########
nifi-commons/nifi-flow-encryptor/src/test/java/org/apache/nifi/flow/encryptor/StandardFlowEncryptorTest.java:
##########
@@ -89,7 +100,145 @@ public void testProcessNoEncrypted() {
assertEquals(property, outputProperty);
}
+ @Test
+ public void testProcessJson() throws JsonProcessingException {
+ final String password = "This is a bad password";
Review Comment:
Recommend changing the value to something like
`String.class.getSimpleName()`.
```suggestion
final String password = String.class.getSimpleName();
```
##########
nifi-commons/nifi-flow-encryptor/src/main/java/org/apache/nifi/flow/encryptor/StandardFlowEncryptor.java:
##########
@@ -49,38 +42,27 @@ public class StandardFlowEncryptor implements FlowEncryptor
{
*/
@Override
public void processFlow(final InputStream inputStream, final OutputStream
outputStream, final PropertyEncryptor inputEncryptor, final PropertyEncryptor
outputEncryptor) {
- try (final PrintWriter writer = new PrintWriter(new
OutputStreamWriter(outputStream))) {
- try (final BufferedReader reader = new BufferedReader(new
InputStreamReader(inputStream))) {
- reader.lines().forEach(line -> {
- final Matcher matcher = ENCRYPTED_PATTERN.matcher(line);
-
- final StringBuffer sb = new StringBuffer();
- boolean matched = false;
- while (matcher.find()) {
- final String outputEncrypted =
getOutputEncrypted(matcher.group(FIRST_GROUP), inputEncryptor, outputEncryptor);
- matcher.appendReplacement(sb, outputEncrypted);
- matched = true;
- }
-
- final String outputLine;
- if (matched) {
- matcher.appendTail(sb);
- outputLine = sb.toString();
- } else {
- outputLine = line;
- }
-
- writer.println(outputLine);
- });
+ final BufferedInputStream bufferedInputStream = new
BufferedInputStream(inputStream);
+ if (bufferedInputStream.markSupported()) {
+ final byte[] firstByte = new byte[1];
+ bufferedInputStream.mark(1);
+ try {
+ bufferedInputStream.read(firstByte, 0, 1);
+ bufferedInputStream.reset();
+ // Flow must be XML or JSON
+ if (firstByte[0] == '<') {
+ final XmlFlowEncryptor flowEncryptor = new
XmlFlowEncryptor();
+ flowEncryptor.processFlow(bufferedInputStream,
outputStream, inputEncryptor, outputEncryptor);
+ } else {
+ final JsonFlowEncryptor flowEncryptor = new
JsonFlowEncryptor();
+ flowEncryptor.processFlow(bufferedInputStream,
outputStream, inputEncryptor, outputEncryptor);
+ }
+ } catch (IOException e) {
+ logger.error(e.getMessage());
Review Comment:
The logger should be removed and replaced with throwing an
`UncheckedIOException` that wraps the `IOException`.
##########
nifi-commons/nifi-flow-encryptor/src/test/java/org/apache/nifi/flow/encryptor/StandardFlowEncryptorTest.java:
##########
@@ -89,7 +100,145 @@ public void testProcessNoEncrypted() {
assertEquals(property, outputProperty);
}
+ @Test
+ public void testProcessJson() throws JsonProcessingException {
+ final String password = "This is a bad password";
+ final String encryptedPassword = String.format(ENCRYPTED_FORMAT,
inputEncryptor.encrypt(password));
+
+ final String sampleFlowJson = getSampleFlowJson(encryptedPassword);
+
+ final InputStream inputStream = new
ByteArrayInputStream(sampleFlowJson.getBytes(StandardCharsets.UTF_8));
+ final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
Review Comment:
Recommend declaring these inside a try-with-resources block for automatic
closing.
##########
nifi-commons/nifi-flow-encryptor/src/test/java/org/apache/nifi/flow/encryptor/StandardFlowEncryptorTest.java:
##########
@@ -89,7 +100,145 @@ public void testProcessNoEncrypted() {
assertEquals(property, outputProperty);
}
+ @Test
+ public void testProcessJson() throws JsonProcessingException {
+ final String password = "This is a bad password";
+ final String encryptedPassword = String.format(ENCRYPTED_FORMAT,
inputEncryptor.encrypt(password));
+
+ final String sampleFlowJson = getSampleFlowJson(encryptedPassword);
+
+ final InputStream inputStream = new
ByteArrayInputStream(sampleFlowJson.getBytes(StandardCharsets.UTF_8));
+ final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ flowEncryptor.processFlow(inputStream, outputStream, inputEncryptor,
outputEncryptor);
+
+ final ObjectMapper mapper = new ObjectMapper();
+
+ final String outputFlowJson = new String(outputStream.toByteArray());
+
+ assertNotEquals(mapper.readTree(sampleFlowJson),
mapper.readTree(outputFlowJson));
+
+ final JsonNode outputJsonNode = mapper.readTree(outputFlowJson);
+
+ final String outputEncryptedPassword = outputJsonNode
+ .path("processors")
+ .get(0)
+ .path("properties")
+ .path("password")
+ .asText();
+
+ final Matcher outputMatcher = PATTERN.matcher(outputEncryptedPassword);
+ assertTrue(outputMatcher.matches());
+ assertEquals(password,
outputEncryptor.decrypt(outputMatcher.group(1)));
+ }
+
+ @Test
+ public void testProcessXml() {
+ final String password = "This is a bad password!";
+ final String encryptedPassword = String.format(ENCRYPTED_FORMAT,
inputEncryptor.encrypt(password));
+ final String sampleFlowXml = getSampleFlowXml(encryptedPassword);
+ final InputStream inputStream = new
ByteArrayInputStream(sampleFlowXml.getBytes(StandardCharsets.UTF_8));
+ final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ flowEncryptor.processFlow(inputStream, outputStream, inputEncryptor,
outputEncryptor);
+
+ final String outputXml = new String(outputStream.toByteArray());
+
+ final Source control = Input.fromString(sampleFlowXml).build();
+ final Source test = Input.fromString(outputXml).build();
+ DifferenceEngine diff = new DOMDifferenceEngine();
+ diff.addDifferenceListener((comparison, outcome) -> {
+ final Matcher inputMatcher =
PATTERN.matcher(comparison.getControlDetails().getValue().toString());
+ final Matcher outputMatcher =
PATTERN.matcher(comparison.getTestDetails().getValue().toString());
+ if (inputMatcher.matches() && outputMatcher.matches()) {
+ assertEquals(inputEncryptor.decrypt(inputMatcher.group(1)),
outputEncryptor.decrypt(outputMatcher.group(1)));
+ }
+ });
+ diff.compare(control, test);
+ }
+
private PropertyEncryptor getPropertyEncryptor(final String propertiesKey,
final String propertiesAlgorithm) {
return new
PropertyEncryptorBuilder(propertiesKey).setAlgorithm(propertiesAlgorithm).build();
}
+
+ private String getSampleFlowJson(final String password) {
+ Objects.requireNonNull(password);
+ return String.format("{\n" +
+ " \"processors\": [\n" +
+ " {\n" +
+ " \"name\": \"SAMPLE_PROCESSOR\",\n" +
+ " \"comments\": \"\",\n" +
+ " \"type\":
\"org.apache.nifi.processors.SAMPLE_PROCESSOR\",\n" +
+ " \"bundle\": {\n" +
+ " \"group\": \"org.apache.nifi\",\n" +
+ " \"artifact\": \"nifi-sample-bundle\"\n" +
+ " },\n" +
+ " \"properties\": {\n" +
+ " \"Commit Offsets\": \"true\",\n" +
+ " \"bootstrap.servers\": \"localhost:9092\",\n" +
+ " \"topic_type\": \"names\",\n" +
+ " \"max-uncommit-offset-wait\": \"1 secs\",\n" +
+ " \"sasl.mechanism\": \"GSSAPI\",\n" +
+ " \"honor-transactions\": \"true\",\n" +
+ " \"message-header-encoding\": \"UTF-8\",\n" +
+ " \"max.poll.records\": \"10000\",\n" +
+ " \"security.protocol\": \"PLAINTEXT\",\n" +
+ " \"sasl.token.auth\": \"false\",\n" +
+ " \"separate-by-key\": \"false\",\n" +
+ " \"username\": \"sample_username\",\n" +
+ " \"password\": \"%s\",\n" +
+ " \"sample_integer\": 10,\n" +
+ " \"sample_float\": 2.5,\n" +
+ " \"sample_true\": true,\n" +
+ " \"sample_false\": false,\n" +
+ " \"sample_empty_array\": [],\n" +
+ " \"key-attribute-encoding\": \"utf-8\",\n" +
+ " \"auto.offset.reset\": \"latest\"\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
Review Comment:
This example contains a large number of unnecessary elements. Recommend
reducing the example to just two properties.
##########
nifi-commons/nifi-flow-encryptor/src/test/java/org/apache/nifi/flow/encryptor/StandardFlowEncryptorTest.java:
##########
@@ -89,7 +100,145 @@ public void testProcessNoEncrypted() {
assertEquals(property, outputProperty);
}
+ @Test
+ public void testProcessJson() throws JsonProcessingException {
+ final String password = "This is a bad password";
+ final String encryptedPassword = String.format(ENCRYPTED_FORMAT,
inputEncryptor.encrypt(password));
+
+ final String sampleFlowJson = getSampleFlowJson(encryptedPassword);
+
+ final InputStream inputStream = new
ByteArrayInputStream(sampleFlowJson.getBytes(StandardCharsets.UTF_8));
+ final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ flowEncryptor.processFlow(inputStream, outputStream, inputEncryptor,
outputEncryptor);
+
+ final ObjectMapper mapper = new ObjectMapper();
+
+ final String outputFlowJson = new String(outputStream.toByteArray());
+
+ assertNotEquals(mapper.readTree(sampleFlowJson),
mapper.readTree(outputFlowJson));
+
+ final JsonNode outputJsonNode = mapper.readTree(outputFlowJson);
+
+ final String outputEncryptedPassword = outputJsonNode
+ .path("processors")
+ .get(0)
+ .path("properties")
+ .path("password")
+ .asText();
+
+ final Matcher outputMatcher = PATTERN.matcher(outputEncryptedPassword);
+ assertTrue(outputMatcher.matches());
+ assertEquals(password,
outputEncryptor.decrypt(outputMatcher.group(1)));
+ }
+
+ @Test
+ public void testProcessXml() {
+ final String password = "This is a bad password!";
+ final String encryptedPassword = String.format(ENCRYPTED_FORMAT,
inputEncryptor.encrypt(password));
+ final String sampleFlowXml = getSampleFlowXml(encryptedPassword);
+ final InputStream inputStream = new
ByteArrayInputStream(sampleFlowXml.getBytes(StandardCharsets.UTF_8));
+ final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ flowEncryptor.processFlow(inputStream, outputStream, inputEncryptor,
outputEncryptor);
+
+ final String outputXml = new String(outputStream.toByteArray());
+
+ final Source control = Input.fromString(sampleFlowXml).build();
+ final Source test = Input.fromString(outputXml).build();
+ DifferenceEngine diff = new DOMDifferenceEngine();
+ diff.addDifferenceListener((comparison, outcome) -> {
+ final Matcher inputMatcher =
PATTERN.matcher(comparison.getControlDetails().getValue().toString());
+ final Matcher outputMatcher =
PATTERN.matcher(comparison.getTestDetails().getValue().toString());
+ if (inputMatcher.matches() && outputMatcher.matches()) {
+ assertEquals(inputEncryptor.decrypt(inputMatcher.group(1)),
outputEncryptor.decrypt(outputMatcher.group(1)));
+ }
+ });
+ diff.compare(control, test);
+ }
+
private PropertyEncryptor getPropertyEncryptor(final String propertiesKey,
final String propertiesAlgorithm) {
return new
PropertyEncryptorBuilder(propertiesKey).setAlgorithm(propertiesAlgorithm).build();
}
+
+ private String getSampleFlowJson(final String password) {
+ Objects.requireNonNull(password);
+ return String.format("{\n" +
+ " \"processors\": [\n" +
+ " {\n" +
+ " \"name\": \"SAMPLE_PROCESSOR\",\n" +
+ " \"comments\": \"\",\n" +
+ " \"type\":
\"org.apache.nifi.processors.SAMPLE_PROCESSOR\",\n" +
+ " \"bundle\": {\n" +
+ " \"group\": \"org.apache.nifi\",\n" +
+ " \"artifact\": \"nifi-sample-bundle\"\n" +
+ " },\n" +
+ " \"properties\": {\n" +
+ " \"Commit Offsets\": \"true\",\n" +
+ " \"bootstrap.servers\": \"localhost:9092\",\n" +
+ " \"topic_type\": \"names\",\n" +
+ " \"max-uncommit-offset-wait\": \"1 secs\",\n" +
+ " \"sasl.mechanism\": \"GSSAPI\",\n" +
+ " \"honor-transactions\": \"true\",\n" +
+ " \"message-header-encoding\": \"UTF-8\",\n" +
+ " \"max.poll.records\": \"10000\",\n" +
+ " \"security.protocol\": \"PLAINTEXT\",\n" +
+ " \"sasl.token.auth\": \"false\",\n" +
+ " \"separate-by-key\": \"false\",\n" +
+ " \"username\": \"sample_username\",\n" +
+ " \"password\": \"%s\",\n" +
+ " \"sample_integer\": 10,\n" +
+ " \"sample_float\": 2.5,\n" +
+ " \"sample_true\": true,\n" +
+ " \"sample_false\": false,\n" +
+ " \"sample_empty_array\": [],\n" +
+ " \"key-attribute-encoding\": \"utf-8\",\n" +
+ " \"auto.offset.reset\": \"latest\"\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }", password);
+ }
+
+ private String getSampleFlowXml(final String password) {
+ Objects.requireNonNull(password);
+ return String.format("<?xml version=\"1.0\" encoding=\"UTF-8\"
standalone=\"no\"?>\n" +
+ "<!--SAMPLE_COMMENT-->\n" +
+ "<flowController encoding-version=\"1.0\">\n" +
+ " <rootGroup>\n" +
+ " <id>fcf146b2-0157-1000-7850-7adf1d31e3fa</id>\n" +
+ " <name>NiFi Flow</name>\n" +
+ " <position x=\"0.0\" y=\"0.0\"/>\n" +
+ " <comment/>\n" +
+ " <processGroup>\n" +
+ " <id>8a61ec1d-0158-1000-3a1a-12c54fe77838</id>\n" +
+ "
<name>org.apache.nifi.processors.SAMPLE_PROCESSORS</name>\n" +
+ " <position x=\"1119.0\" y=\"295.0\"/>\n" +
+ " <comment/>\n" +
+ " <processor>\n" +
+ "
<id>8a621f0b-0158-1000-b5c2-92a09a124501</id>\n" +
+ " <name>Encrypt</name>\n" +
+ " <position x=\"626.0\" y=\"237.0\"/>\n" +
+ " <styles/>\n" +
+ " <comment/>\n" +
+ "
<class>org.apache.nifi.processors.standard.EncryptContent</class>\n" +
+ " <maxConcurrentTasks>1</maxConcurrentTasks>\n"
+
+ " <schedulingPeriod>0 sec</schedulingPeriod>\n"
+
+ " <yieldPeriod>1 sec</yieldPeriod>\n" +
+ " <bulletinLevel>WARN</bulletinLevel>\n" +
+ " <lossTolerant>false</lossTolerant>\n" +
+ " <runDurationNanos>0</runDurationNanos>\n" +
+ " <property>\n" +
+ " <name>Username</name>\n" +
+ " <value>SAMPLE_USERNAME</value>\n" +
+ " </property>\n" +
+ " <property>\n" +
+ " <name>Password</name>\n" +
+ " <value>%s</value>\n" +
+ " </property>\n" +
+ " </processor>\n" +
+ " </processGroup>\n" +
+ " </rootGroup>\n" +
+ " <controllerServices/>\n" +
+ " <reportingTasks/>\n" +
+ "</flowController>", password);
Review Comment:
Maintaining this much inline XML can be cumbersome, it is probably better to
externalize this example, or use code to generate a simple XML string.
--
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]