This is an automated email from the ASF dual-hosted git repository.

ijokarumawak pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nifi.git


The following commit(s) were added to refs/heads/master by this push:
     new f1ae059  NIFI-6004 PutFile created directory permissions
f1ae059 is described below

commit f1ae05974e305b8487b26a87338d2faf9c2d851f
Author: adyoun2 <[email protected]>
AuthorDate: Thu Feb 7 16:41:48 2019 +0000

    NIFI-6004 PutFile created directory permissions
    
    NIFI-6004 Improve testing of PutFile file and directory permissions
    
    NIFI-6004 Typo in regex
    
    NIFI-6004 Updates based on review
    
    This closes #3294.
    
    Signed-off-by: Koji Kawamura <[email protected]>
---
 .../apache/nifi/processors/standard/PutFile.java   | 128 ++++++++++++++++++---
 .../nifi/processors/standard/TestPutFile.java      | 116 +++++++++++++++++--
 2 files changed, 220 insertions(+), 24 deletions(-)

diff --git 
a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/PutFile.java
 
b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/PutFile.java
index 8784c75..73caa69 100644
--- 
a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/PutFile.java
+++ 
b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/PutFile.java
@@ -28,6 +28,9 @@ import org.apache.nifi.annotation.documentation.SeeAlso;
 import org.apache.nifi.annotation.documentation.Tags;
 import org.apache.nifi.components.PropertyDescriptor;
 import org.apache.nifi.components.RequiredPermission;
+import org.apache.nifi.components.ValidationContext;
+import org.apache.nifi.components.ValidationResult;
+import org.apache.nifi.components.Validator;
 import org.apache.nifi.expression.ExpressionLanguageScope;
 import org.apache.nifi.flowfile.FlowFile;
 import org.apache.nifi.flowfile.attributes.CoreAttributes;
@@ -58,6 +61,7 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 @EventDriven
@@ -83,6 +87,28 @@ public class PutFile extends AbstractProcessor {
     public static final String FILE_MODIFY_DATE_ATTRIBUTE = 
"file.lastModifiedTime";
     public static final String FILE_MODIFY_DATE_ATTR_FORMAT = 
"yyyy-MM-dd'T'HH:mm:ssZ";
 
+    public static final Pattern RWX_PATTERN = 
Pattern.compile("^([r-][w-])([x-])([r-][w-])([x-])([r-][w-])([x-])$");
+    public static final Pattern NUM_PATTERN = Pattern.compile("^[0-7]{3}$");
+
+    private static final Validator PERMISSIONS_VALIDATOR = new Validator() {
+        @Override
+        public ValidationResult validate(String subject, String input, 
ValidationContext context) {
+            ValidationResult.Builder vr = new ValidationResult.Builder();
+            if (context.isExpressionLanguagePresent(input)) {
+                return new 
ValidationResult.Builder().subject(subject).input(input).explanation("Expression
 Language Present").valid(true).build();
+            }
+
+            if (RWX_PATTERN.matcher(input).matches() || 
NUM_PATTERN.matcher(input).matches()) {
+                return vr.valid(true).build();
+            }
+            return vr.valid(false)
+                    .subject(subject)
+                    .input(input)
+                    .explanation("This must be expressed in rwxr-x--- form or 
octal triplet form.")
+                    .build();
+        }
+    };
+
     public static final PropertyDescriptor DIRECTORY = new 
PropertyDescriptor.Builder()
             .name("Directory")
             .description("The directory to which files should be written. You 
may use expression language such as /aa/bb/${path}")
@@ -117,13 +143,13 @@ public class PutFile extends AbstractProcessor {
                     + "place of denied permissions (e.g. rw-r--r--) or an 
octal number (e.g. 644).  You may also use expression language such as "
                     + "${file.permissions}.")
             .required(false)
-            .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
+            .addValidator(PERMISSIONS_VALIDATOR)
             
.expressionLanguageSupported(ExpressionLanguageScope.FLOWFILE_ATTRIBUTES)
             .build();
     public static final PropertyDescriptor CHANGE_OWNER = new 
PropertyDescriptor.Builder()
             .name("Owner")
             .description("Sets the owner on the output file to the value of 
this attribute.  You may also use expression language such as "
-                    + "${file.owner}.")
+                    + "${file.owner}. Note on many operating systems Nifi must 
be running as a super-user to have the permissions to set the file owner.")
             .required(false)
             .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
             
.expressionLanguageSupported(ExpressionLanguageScope.FLOWFILE_ATTRIBUTES)
@@ -208,9 +234,58 @@ public class PutFile extends AbstractProcessor {
             final Path tempCopyFile = rootDirPath.resolve("." + filename);
             final Path copyFile = rootDirPath.resolve(filename);
 
+            final String permissions = 
context.getProperty(CHANGE_PERMISSIONS).evaluateAttributeExpressions(flowFile).getValue();
+            final String owner = 
context.getProperty(CHANGE_OWNER).evaluateAttributeExpressions(flowFile).getValue();
+            final String group = 
context.getProperty(CHANGE_GROUP).evaluateAttributeExpressions(flowFile).getValue();
             if (!Files.exists(rootDirPath)) {
                 if (context.getProperty(CREATE_DIRS).asBoolean()) {
-                    Files.createDirectories(rootDirPath);
+                    Path existing = rootDirPath;
+                    while (!Files.exists(existing)) {
+                        existing = existing.getParent();
+                    }
+                    if (permissions != null && !permissions.trim().isEmpty()) {
+                        try {
+                            String perms = stringPermissions(permissions, 
true);
+                            if (!perms.isEmpty()) {
+                                Files.createDirectories(rootDirPath, 
PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString(perms)));
+                            } else {
+                                Files.createDirectories(rootDirPath);
+                            }
+                        } catch (Exception e) {
+                            flowFile = session.penalize(flowFile);
+                            session.transfer(flowFile, REL_FAILURE);
+                            logger.error("Could not set create directory with 
permissions {} because {}", new Object[]{permissions, e});
+                            return;
+                        }
+                    } else {
+                        Files.createDirectories(rootDirPath);
+                    }
+
+                    boolean chOwner = owner != null && !owner.trim().isEmpty();
+                    boolean chGroup = group != null && !group.trim().isEmpty();
+                    if (chOwner || chGroup) {
+                        Path currentPath = rootDirPath;
+                        while (!currentPath.equals(existing)) {
+                            if (chOwner) {
+                                try {
+                                    UserPrincipalLookupService lookupService = 
currentPath.getFileSystem().getUserPrincipalLookupService();
+                                    Files.setOwner(currentPath, 
lookupService.lookupPrincipalByName(owner));
+                                } catch (Exception e) {
+                                    logger.warn("Could not set directory owner 
to {} because {}", new Object[]{owner, e});
+                                }
+                            }
+                            if (chGroup) {
+                                try {
+                                    UserPrincipalLookupService lookupService = 
currentPath.getFileSystem().getUserPrincipalLookupService();
+                                    PosixFileAttributeView view = 
Files.getFileAttributeView(currentPath, PosixFileAttributeView.class);
+                                    
view.setGroup(lookupService.lookupPrincipalByGroupName(group));
+                                } catch (Exception e) {
+                                    logger.warn("Could not set file group to 
{} because {}", new Object[]{group, e});
+                                }
+                            }
+                            currentPath = currentPath.getParent();
+                        }
+                    }
                 } else {
                     flowFile = session.penalize(flowFile);
                     session.transfer(flowFile, REL_FAILURE);
@@ -270,10 +345,9 @@ public class PutFile extends AbstractProcessor {
                 }
             }
 
-            final String permissions = 
context.getProperty(CHANGE_PERMISSIONS).evaluateAttributeExpressions(flowFile).getValue();
             if (permissions != null && !permissions.trim().isEmpty()) {
                 try {
-                    String perms = stringPermissions(permissions);
+                    String perms = stringPermissions(permissions, false);
                     if (!perms.isEmpty()) {
                         Files.setPosixFilePermissions(dotCopyFile, 
PosixFilePermissions.fromString(perms));
                     }
@@ -282,7 +356,6 @@ public class PutFile extends AbstractProcessor {
                 }
             }
 
-            final String owner = 
context.getProperty(CHANGE_OWNER).evaluateAttributeExpressions(flowFile).getValue();
             if (owner != null && !owner.trim().isEmpty()) {
                 try {
                     UserPrincipalLookupService lookupService = 
dotCopyFile.getFileSystem().getUserPrincipalLookupService();
@@ -292,7 +365,6 @@ public class PutFile extends AbstractProcessor {
                 }
             }
 
-            final String group = 
context.getProperty(CHANGE_GROUP).evaluateAttributeExpressions(flowFile).getValue();
             if (group != null && !group.trim().isEmpty()) {
                 try {
                     UserPrincipalLookupService lookupService = 
dotCopyFile.getFileSystem().getUserPrincipalLookupService();
@@ -345,13 +417,24 @@ public class PutFile extends AbstractProcessor {
                 .count();
     }
 
-    protected String stringPermissions(String perms) {
+    protected String stringPermissions(String perms, boolean directory) {
         String permissions = "";
-        final Pattern rwxPattern = Pattern.compile("^[rwx-]{9}$");
-        final Pattern numPattern = Pattern.compile("\\d+");
-        if (rwxPattern.matcher(perms).matches()) {
-            permissions = perms;
-        } else if (numPattern.matcher(perms).matches()) {
+        Matcher rwx = RWX_PATTERN.matcher(perms);
+        if (rwx.matches()) {
+            if (directory) {
+                // To read or write, directory access will be required
+                StringBuilder permBuilder = new StringBuilder();
+                permBuilder.append("$1");
+                permBuilder.append(rwx.group(1).equals("--") ? "$2" : "x");
+                permBuilder.append("$3");
+                permBuilder.append(rwx.group(3).equals("--") ? "$4" : "x");
+                permBuilder.append("$5");
+                permBuilder.append(rwx.group(5).equals("--") ? "$6" : "x");
+                permissions = rwx.replaceAll(permBuilder.toString());
+            } else {
+                permissions = perms;
+            }
+        } else if (NUM_PATTERN.matcher(perms).matches()) {
             try {
                 int number = Integer.parseInt(perms, 8);
                 StringBuilder permBuilder = new StringBuilder();
@@ -365,7 +448,7 @@ public class PutFile extends AbstractProcessor {
                 } else {
                     permBuilder.append('-');
                 }
-                if ((number & 0x40) > 0) {
+                if (directory || (number & 0x40) > 0) {
                     permBuilder.append('x');
                 } else {
                     permBuilder.append('-');
@@ -383,7 +466,12 @@ public class PutFile extends AbstractProcessor {
                 if ((number & 0x8) > 0) {
                     permBuilder.append('x');
                 } else {
-                    permBuilder.append('-');
+                    if (directory && (number & 0x30) > 0) {
+                        // To read or write, directory access will be required
+                        permBuilder.append('x');
+                    } else {
+                        permBuilder.append('-');
+                    }
                 }
                 if ((number & 0x4) > 0) {
                     permBuilder.append('r');
@@ -395,15 +483,21 @@ public class PutFile extends AbstractProcessor {
                 } else {
                     permBuilder.append('-');
                 }
-                if ((number & 0x8) > 0) {
+                if ((number & 0x1) > 0) {
                     permBuilder.append('x');
                 } else {
-                    permBuilder.append('-');
+                    if (directory && (number & 0x6) > 0) {
+                        // To read or write, directory access will be required
+                        permBuilder.append('x');
+                    } else {
+                        permBuilder.append('-');
+                    }
                 }
                 permissions = permBuilder.toString();
             } catch (NumberFormatException ignore) {
             }
         }
+
         return permissions;
     }
 }
diff --git 
a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestPutFile.java
 
b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestPutFile.java
index 51ad7c0..3440c44 100644
--- 
a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestPutFile.java
+++ 
b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestPutFile.java
@@ -14,24 +14,31 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package org.apache.nifi.processors.standard;
 
-import static org.junit.Assert.assertEquals;
+import org.apache.nifi.flowfile.attributes.CoreAttributes;
+import org.apache.nifi.util.TestRunner;
+import org.apache.nifi.util.TestRunners;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
 
 import java.io.File;
 import java.io.IOException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.FileVisitor;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.PosixFileAttributeView;
+import java.nio.file.attribute.PosixFilePermission;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Set;
 
-import org.apache.nifi.flowfile.attributes.CoreAttributes;
-import org.apache.nifi.util.TestRunner;
-import org.apache.nifi.util.TestRunners;
-import org.junit.Before;
-import org.junit.Test;
+import static org.junit.Assert.assertEquals;
 
 public class TestPutFile {
 
@@ -207,4 +214,99 @@ public class TestPutFile {
         assertEquals("Another file", new String(content));
     }
 
+    private TestRunner putFileRunner;
+
+    private final String testFile = "src" + File.separator + "test" + 
File.separator + "resources" + File.separator + "hello.txt";
+
+    @Before
+    public void setup() throws IOException{
+
+        putFileRunner = TestRunners.newTestRunner(PutFile.class);
+        putFileRunner.setProperty(PutFile.CHANGE_OWNER, 
System.getProperty("user.name"));
+        putFileRunner.setProperty(PutFile.CHANGE_PERMISSIONS, "rw-r-----");
+        putFileRunner.setProperty(PutFile.CREATE_DIRS, "true");
+        putFileRunner.setProperty(PutFile.DIRECTORY, 
"target/test/data/out/PutFile/1/2/3/4/5");
+
+        putFileRunner.setValidateExpressionUsage(false);
+    }
+
+    @After
+    public void tearDown() throws IOException {
+        emptyTestDirectory();
+    }
+
+    @Test
+    public void testPutFile() throws IOException {
+        emptyTestDirectory();
+
+        Map<String,String> attributes = new HashMap<>();
+        attributes.put("filename", "testfile.txt");
+
+        putFileRunner.enqueue(Paths.get(testFile), attributes);
+        putFileRunner.run();
+
+        putFileRunner.assertTransferCount(PutSFTP.REL_SUCCESS, 1);
+
+        //verify directory exists
+        Path newDirectory = 
Paths.get("target/test/data/out/PutFile/1/2/3/4/5");
+        Path newFile = newDirectory.resolve("testfile.txt");
+        Assert.assertTrue("New directory not created.", 
newDirectory.toAbsolutePath().toFile().exists());
+        Assert.assertTrue("New File not created.", 
newFile.toAbsolutePath().toFile().exists());
+
+        PosixFileAttributeView filePosixAttributeView = 
Files.getFileAttributeView(newFile.toAbsolutePath(), 
PosixFileAttributeView.class);
+        Assert.assertEquals(System.getProperty("user.name"), 
filePosixAttributeView.getOwner().getName());
+        Set<PosixFilePermission> filePermissions = 
filePosixAttributeView.readAttributes().permissions();
+        
Assert.assertTrue(filePermissions.contains(PosixFilePermission.OWNER_READ));
+        
Assert.assertTrue(filePermissions.contains(PosixFilePermission.OWNER_WRITE));
+        
Assert.assertFalse(filePermissions.contains(PosixFilePermission.OWNER_EXECUTE));
+        
Assert.assertTrue(filePermissions.contains(PosixFilePermission.GROUP_READ));
+        
Assert.assertFalse(filePermissions.contains(PosixFilePermission.GROUP_WRITE));
+        
Assert.assertFalse(filePermissions.contains(PosixFilePermission.GROUP_EXECUTE));
+        
Assert.assertFalse(filePermissions.contains(PosixFilePermission.OTHERS_READ));
+        
Assert.assertFalse(filePermissions.contains(PosixFilePermission.OTHERS_WRITE));
+        
Assert.assertFalse(filePermissions.contains(PosixFilePermission.OTHERS_EXECUTE));
+
+        PosixFileAttributeView dirPosixAttributeView = 
Files.getFileAttributeView(newDirectory.toAbsolutePath(), 
PosixFileAttributeView.class);
+        Assert.assertEquals(System.getProperty("user.name"), 
dirPosixAttributeView.getOwner().getName());
+        Set<PosixFilePermission> dirPermissions = 
dirPosixAttributeView.readAttributes().permissions();
+        
Assert.assertTrue(dirPermissions.contains(PosixFilePermission.OWNER_READ));
+        
Assert.assertTrue(dirPermissions.contains(PosixFilePermission.OWNER_WRITE));
+        
Assert.assertTrue(dirPermissions.contains(PosixFilePermission.OWNER_EXECUTE));
+        
Assert.assertTrue(dirPermissions.contains(PosixFilePermission.GROUP_READ));
+        
Assert.assertFalse(dirPermissions.contains(PosixFilePermission.GROUP_WRITE));
+        
Assert.assertTrue(dirPermissions.contains(PosixFilePermission.GROUP_EXECUTE));
+        
Assert.assertFalse(dirPermissions.contains(PosixFilePermission.OTHERS_READ));
+        
Assert.assertFalse(dirPermissions.contains(PosixFilePermission.OTHERS_WRITE));
+        
Assert.assertFalse(dirPermissions.contains(PosixFilePermission.OTHERS_EXECUTE));
+
+        putFileRunner.clearTransferState();
+    }
+
+
+    private void emptyTestDirectory() throws IOException {
+        Files.walkFileTree(Paths.get("target/test/data/out/PutFile"), new 
FileVisitor<Path>() {
+
+            @Override
+            public FileVisitResult preVisitDirectory(Path dir, 
BasicFileAttributes attrs) throws IOException {
+                return FileVisitResult.CONTINUE;
+            }
+
+            @Override
+            public FileVisitResult visitFile(Path file, BasicFileAttributes 
attrs) throws IOException {
+                Files.delete(file);
+                return FileVisitResult.CONTINUE;
+            }
+
+            @Override
+            public FileVisitResult visitFileFailed(Path file, IOException exc) 
throws IOException {
+                return FileVisitResult.CONTINUE;
+            }
+
+            @Override
+            public FileVisitResult postVisitDirectory(Path dir, IOException 
exc) throws IOException {
+                Files.delete(dir);
+                return FileVisitResult.CONTINUE;
+            }
+        });
+    }
 }

Reply via email to