| /* |
| * 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.rya.api.path; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.nio.file.FileSystem; |
| import java.nio.file.Files; |
| import java.nio.file.Path; |
| import java.nio.file.Paths; |
| import java.nio.file.attribute.UserPrincipal; |
| import java.nio.file.attribute.UserPrincipalLookupService; |
| |
| import org.apache.commons.io.FilenameUtils; |
| import org.apache.commons.lang.SystemUtils; |
| import org.apache.hadoop.conf.Configuration; |
| |
| /** |
| * Utility methods for {@link Path}s. |
| */ |
| public final class PathUtils { |
| /** |
| * Private constructor to prevent instantiation. |
| */ |
| private PathUtils() { |
| } |
| |
| /** |
| * Cleans the path to prevent path manipulation. It performs the following: |
| * <ul> |
| * <li>Normalizes a path, removing double and single dot path steps.</li> |
| * </ul> |
| * @param path the path to clean. |
| * @return the cleansed path. |
| * @throws IllegalArgumentException if file is in a shared directory. |
| */ |
| public static Path cleanPath(final Path path) { |
| if (path != null) { |
| final Path cleanPath = cleanPath(path.toString()); |
| return cleanPath; |
| } |
| return null; |
| } |
| |
| /** |
| * Cleans the path to prevent path manipulation. It performs the following: |
| * <ul> |
| * <li>Normalizes a path, removing double and single dot path steps.</li> |
| * </ul> |
| * @param filename the filename to clean. |
| * @return the cleansed path. |
| * @throws IllegalArgumentException if file is in a shared directory. |
| */ |
| public static Path cleanPath(final String filename) { |
| if (filename != null) { |
| final Path cleanPath = Paths.get(clean(filename)); |
| return cleanPath; |
| } |
| return null; |
| } |
| |
| /** |
| * Cleans the path to prevent path manipulation. It performs the following: |
| * <ul> |
| * <li>Normalizes a path, removing double and single dot path steps.</li> |
| * <li>Ensures path is not in shared directory.</li> |
| * </ul> |
| * @param filename the filename to clean. |
| * @return the cleansed path. |
| * @throws IllegalArgumentException if file is in a shared directory. |
| * @throws IOException |
| */ |
| public static org.apache.hadoop.fs.Path cleanHadoopPath(final org.apache.hadoop.fs.Path hadoopPath, final Configuration conf) throws IllegalArgumentException, IOException { |
| if (hadoopPath != null) { |
| final Path path = fromHadoopPath(hadoopPath, conf); |
| final Path clean = cleanPath(path); |
| return toHadoopPath(clean); |
| } |
| return null; |
| } |
| |
| /** |
| * Cleans the path to prevent path manipulation. It performs the following: |
| * <ul> |
| * <li>Normalizes a path, removing double and single dot path steps.</li> |
| * <li>Ensures path is not in shared directory.</li> |
| * </ul> |
| * @param filename the filename to clean. |
| * @return the cleansed path. |
| * @throws IllegalArgumentException if file is in a shared directory. |
| */ |
| public static String clean(final String filename) throws IllegalArgumentException { |
| if (filename != null) { |
| final String clean = FilenameUtils.normalize(filename); |
| if (!isInSecureDir(clean)) { |
| throw new IllegalArgumentException("Operation of a file in a shared directory is not allowed: " + filename); |
| } |
| return clean; |
| } |
| return null; |
| } |
| |
| /** |
| * Indicates whether file lives in a secure directory relative to the |
| * program's user. |
| * @param filename the filename to test. |
| * @return {@code true} if file's directory is secure. |
| */ |
| public static boolean isInSecureDir(final String filename) { |
| final Path path = filename != null ? Paths.get(filename) : null; |
| return isInSecureDir(path, null); |
| } |
| |
| /** |
| * Indicates whether file lives in a secure directory relative to the |
| * program's user. |
| * @param file {@link Path} to test. |
| * @return {@code true} if file's directory is secure. |
| */ |
| public static boolean isInSecureDir(final Path file) { |
| return isInSecureDir(file, null); |
| } |
| |
| /** |
| * Indicates whether file lives in a secure directory relative to the |
| * program's user. |
| * @param file {@link Path} to test. |
| * @param user {@link UserPrincipal} to test. If {@code null}, defaults to |
| * current user |
| * @return {@code true} if file's directory is secure. |
| */ |
| public static boolean isInSecureDir(final Path file, final UserPrincipal user) { |
| return isInSecureDir(file, user, 5); |
| } |
| |
| /** |
| * Indicates whether file lives in a secure directory relative to the |
| * program's user. |
| * @param file {@link Path} to test. |
| * @param user {@link UserPrincipal} to test. If {@code null}, defaults to |
| * current user. |
| * @param symlinkDepth Number of symbolic links allowed. |
| * @return {@code true} if file's directory is secure. |
| */ |
| public static boolean isInSecureDir(Path file, UserPrincipal user, final int symlinkDepth) { |
| if (!file.isAbsolute()) { |
| file = file.toAbsolutePath(); |
| } |
| if (symlinkDepth <= 0) { |
| // Too many levels of symbolic links |
| return false; |
| } |
| // Get UserPrincipal for specified user and superuser |
| final Path fileRoot = file.getRoot(); |
| if (fileRoot == null) { |
| return false; |
| } |
| final FileSystem fileSystem = Paths.get(fileRoot.toString()).getFileSystem(); |
| final UserPrincipalLookupService upls = fileSystem.getUserPrincipalLookupService(); |
| UserPrincipal root = null; |
| try { |
| if (SystemUtils.IS_OS_UNIX) { |
| root = upls.lookupPrincipalByName("root"); |
| } else { |
| root = upls.lookupPrincipalByName("Administrators"); |
| } |
| if (user == null) { |
| user = upls.lookupPrincipalByName(System.getProperty("user.name")); |
| } |
| if (root == null || user == null) { |
| return false; |
| } |
| } catch (final IOException x) { |
| return false; |
| } |
| // If any parent dirs (from root on down) are not secure, dir is not secure |
| for (int i = 1; i <= file.getNameCount(); i++) { |
| final Path partialPath = Paths.get(fileRoot.toString(), file.subpath(0, i).toString()); |
| try { |
| if (Files.isSymbolicLink(partialPath)) { |
| if (!isInSecureDir(Files.readSymbolicLink(partialPath), user, symlinkDepth - 1)) { |
| // Symbolic link, linked-to dir not secure |
| return false; |
| } |
| } else { |
| final UserPrincipal owner = Files.getOwner(partialPath); |
| if (!user.equals(owner) && !root.equals(owner)) { |
| // dir owned by someone else, not secure |
| return SystemUtils.IS_OS_UNIX ? false : Files.isWritable(partialPath); |
| } |
| } |
| } catch (final IOException x) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Converts a path string to a {@link org.apache.hadoop.fs.Path}. |
| * @param filename The path string |
| * @return the resulting {@link org.apache.hadoop.fs.Path}. |
| */ |
| public static org.apache.hadoop.fs.Path toHadoopPath(final String filename) { |
| if (filename != null) { |
| final Path path = Paths.get(filename); |
| return toHadoopPath(path); |
| } |
| return null; |
| } |
| |
| /** |
| * Converts a {@link Path} to a {@link org.apache.hadoop.fs.Path}. |
| * @param path The {@link Path}. |
| * @return the resulting {@link org.apache.hadoop.fs.Path}. |
| */ |
| public static org.apache.hadoop.fs.Path toHadoopPath(final Path path) { |
| if (path != null) { |
| final String stringPath = FilenameUtils.separatorsToUnix(path.toAbsolutePath().toString()); |
| final org.apache.hadoop.fs.Path hadoopPath = new org.apache.hadoop.fs.Path(stringPath); |
| return hadoopPath; |
| } |
| return null; |
| } |
| |
| /** |
| * Converts a {@link org.apache.hadoop.fs.Path} to a {@link Path}. |
| * @param hadoopPath The {@link org.apache.hadoop.fs.Path}. |
| * @param conf the {@link Configuration}. |
| * @return the resulting {@link org.apache.hadoop.fs.Path}. |
| */ |
| public static Path fromHadoopPath(final org.apache.hadoop.fs.Path hadoopPath, final Configuration conf) throws IOException { |
| if (hadoopPath != null) { |
| final org.apache.hadoop.fs.FileSystem fs = org.apache.hadoop.fs.FileSystem.get(hadoopPath.toUri(), conf); |
| final File tempFile = File.createTempFile(hadoopPath.getName(), ""); |
| tempFile.deleteOnExit(); |
| fs.copyToLocalFile(hadoopPath, new org.apache.hadoop.fs.Path(tempFile.getAbsolutePath())); |
| return tempFile.toPath(); |
| } |
| return null; |
| } |
| |
| |
| } |