HADOOP-14768. Honoring sticky bit during Deletion when authorization is enabled in WASB
Contributed by Varada Hemeswari
This commit is contained in:
parent
8e1bd114ee
commit
a530e7ab3b
|
@ -40,6 +40,8 @@ import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Stack;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.JsonParseException;
|
import com.fasterxml.jackson.core.JsonParseException;
|
||||||
import com.fasterxml.jackson.core.JsonParser;
|
import com.fasterxml.jackson.core.JsonParser;
|
||||||
|
@ -80,6 +82,8 @@ import org.apache.hadoop.util.Time;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import static org.apache.hadoop.fs.azure.NativeAzureFileSystemHelper.*;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import com.microsoft.azure.storage.StorageException;
|
import com.microsoft.azure.storage.StorageException;
|
||||||
|
@ -1849,31 +1853,256 @@ public class NativeAzureFileSystem extends FileSystem {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete the specified file or folder. The parameter
|
* Delete file or folder with authorization checks. Most of the code
|
||||||
* skipParentFolderLastModifiedTimeUpdate
|
* is duplicate of the actual delete implementation and will be merged
|
||||||
* is used in the case of atomic folder rename redo. In that case, there is
|
* once the performance and funcional aspects are guaranteed not to
|
||||||
* a lease on the parent folder, so (without reworking the code) modifying
|
* regress existing delete semantics.
|
||||||
* the parent folder update time will fail because of a conflict with the
|
|
||||||
* lease. Since we are going to delete the folder soon anyway so accurate
|
|
||||||
* modified time is not necessary, it's easier to just skip
|
|
||||||
* the modified time update.
|
|
||||||
*
|
|
||||||
* @param f file path to be deleted.
|
|
||||||
* @param recursive specify deleting recursively or not.
|
|
||||||
* @param skipParentFolderLastModifiedTimeUpdate If true, don't update the folder last
|
|
||||||
* modified time.
|
|
||||||
* @return true if and only if the file is deleted
|
|
||||||
* @throws IOException Thrown when fail to delete file or directory.
|
|
||||||
*/
|
*/
|
||||||
public boolean delete(Path f, boolean recursive,
|
private boolean deleteWithAuthEnabled(Path f, boolean recursive,
|
||||||
boolean skipParentFolderLastModifiedTimeUpdate) throws IOException {
|
boolean skipParentFolderLastModifiedTimeUpdate) throws IOException {
|
||||||
|
|
||||||
LOG.debug("Deleting file: {}", f.toString());
|
LOG.debug("Deleting file: {}", f);
|
||||||
|
|
||||||
Path absolutePath = makeAbsolute(f);
|
Path absolutePath = makeAbsolute(f);
|
||||||
Path parentPath = absolutePath.getParent();
|
Path parentPath = absolutePath.getParent();
|
||||||
|
|
||||||
performAuthCheck(parentPath, WasbAuthorizationOperations.WRITE, "delete", absolutePath);
|
// If delete is issued for 'root', parentPath will be null
|
||||||
|
// In that case, we perform auth check for root itself before
|
||||||
|
// proceeding for deleting contents under root.
|
||||||
|
if (parentPath != null) {
|
||||||
|
performAuthCheck(parentPath, WasbAuthorizationOperations.WRITE, "delete", absolutePath);
|
||||||
|
} else {
|
||||||
|
performAuthCheck(absolutePath, WasbAuthorizationOperations.WRITE, "delete", absolutePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
String key = pathToKey(absolutePath);
|
||||||
|
|
||||||
|
// Capture the metadata for the path.
|
||||||
|
FileMetadata metaFile = null;
|
||||||
|
try {
|
||||||
|
metaFile = store.retrieveMetadata(key);
|
||||||
|
} catch (IOException e) {
|
||||||
|
|
||||||
|
Throwable innerException = checkForAzureStorageException(e);
|
||||||
|
|
||||||
|
if (innerException instanceof StorageException
|
||||||
|
&& isFileNotFoundException((StorageException) innerException)) {
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null == metaFile) {
|
||||||
|
// The path to be deleted does not exist.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
FileMetadata parentMetadata = null;
|
||||||
|
String parentKey = null;
|
||||||
|
if (parentPath != null) {
|
||||||
|
parentKey = pathToKey(parentPath);
|
||||||
|
|
||||||
|
try {
|
||||||
|
parentMetadata = store.retrieveMetadata(parentKey);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Throwable innerException = checkForAzureStorageException(e);
|
||||||
|
if (innerException instanceof StorageException) {
|
||||||
|
// Invalid State.
|
||||||
|
// A FileNotFoundException is not thrown here as the API returns false
|
||||||
|
// if the file not present. But not retrieving metadata here is an
|
||||||
|
// unrecoverable state and can only happen if there is a race condition
|
||||||
|
// hence throwing a IOException
|
||||||
|
if (isFileNotFoundException((StorageException) innerException)) {
|
||||||
|
throw new IOException("File " + f + " has a parent directory "
|
||||||
|
+ parentPath + " whose metadata cannot be retrieved. Can't resolve");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Same case as unable to retrieve metadata
|
||||||
|
if (parentMetadata == null) {
|
||||||
|
throw new IOException("File " + f + " has a parent directory "
|
||||||
|
+ parentPath + " whose metadata cannot be retrieved. Can't resolve");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!parentMetadata.isDir()) {
|
||||||
|
// Invalid state: the parent path is actually a file. Throw.
|
||||||
|
throw new AzureException("File " + f + " has a parent directory "
|
||||||
|
+ parentPath + " which is also a file. Can't resolve.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The path exists, determine if it is a folder containing objects,
|
||||||
|
// an empty folder, or a simple file and take the appropriate actions.
|
||||||
|
if (!metaFile.isDir()) {
|
||||||
|
// The path specifies a file. We need to check the parent path
|
||||||
|
// to make sure it's a proper materialized directory before we
|
||||||
|
// delete the file. Otherwise we may get into a situation where
|
||||||
|
// the file we were deleting was the last one in an implicit directory
|
||||||
|
// (e.g. the blob store only contains the blob a/b and there's no
|
||||||
|
// corresponding directory blob a) and that would implicitly delete
|
||||||
|
// the directory as well, which is not correct.
|
||||||
|
|
||||||
|
if (parentPath != null && parentPath.getParent() != null) {// Not root
|
||||||
|
|
||||||
|
if (parentMetadata.getBlobMaterialization() == BlobMaterialization.Implicit) {
|
||||||
|
LOG.debug("Found an implicit parent directory while trying to"
|
||||||
|
+ " delete the file {}. Creating the directory blob for"
|
||||||
|
+ " it in {}.", f, parentKey);
|
||||||
|
|
||||||
|
store.storeEmptyFolder(parentKey,
|
||||||
|
createPermissionStatus(FsPermission.getDefault()));
|
||||||
|
} else {
|
||||||
|
if (!skipParentFolderLastModifiedTimeUpdate) {
|
||||||
|
updateParentFolderLastModifiedTime(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if the file can be deleted based on sticky bit check
|
||||||
|
// This check will be performed only when authorization is enabled
|
||||||
|
if (isStickyBitCheckViolated(metaFile, parentMetadata)) {
|
||||||
|
throw new WasbAuthorizationException(String.format("%s has sticky bit set. "
|
||||||
|
+ "File %s cannot be deleted.", parentPath, f));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (store.delete(key)) {
|
||||||
|
instrumentation.fileDeleted();
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch(IOException e) {
|
||||||
|
|
||||||
|
Throwable innerException = checkForAzureStorageException(e);
|
||||||
|
|
||||||
|
if (innerException instanceof StorageException
|
||||||
|
&& isFileNotFoundException((StorageException) innerException)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// The path specifies a folder. Recursively delete all entries under the
|
||||||
|
// folder.
|
||||||
|
LOG.debug("Directory Delete encountered: {}", f);
|
||||||
|
if (parentPath != null && parentPath.getParent() != null) {
|
||||||
|
|
||||||
|
if (parentMetadata.getBlobMaterialization() == BlobMaterialization.Implicit) {
|
||||||
|
LOG.debug("Found an implicit parent directory while trying to"
|
||||||
|
+ " delete the directory {}. Creating the directory blob for"
|
||||||
|
+ " it in {}. ", f, parentKey);
|
||||||
|
|
||||||
|
store.storeEmptyFolder(parentKey,
|
||||||
|
createPermissionStatus(FsPermission.getDefault()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if the folder can be deleted based on sticky bit check on parent
|
||||||
|
// This check will be performed only when authorization is enabled.
|
||||||
|
if (!metaFile.getKey().equals("/")
|
||||||
|
&& isStickyBitCheckViolated(metaFile, parentMetadata)) {
|
||||||
|
|
||||||
|
throw new WasbAuthorizationException(String.format("%s has sticky bit set. "
|
||||||
|
+ "File %s cannot be deleted.", parentPath, f));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate through folder contents and get the list of files
|
||||||
|
// and folders that can be deleted. We might encounter IOException
|
||||||
|
// while listing blobs. In such cases, we return false.
|
||||||
|
ArrayList<FileMetadata> fileMetadataList = new ArrayList<>();
|
||||||
|
boolean isPartialDelete = false;
|
||||||
|
|
||||||
|
// Start time for list operation
|
||||||
|
long start = Time.monotonicNow();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get list of files/folders that can be deleted
|
||||||
|
// based on authorization checks and stickybit checks
|
||||||
|
isPartialDelete = getFolderContentsToDelete(metaFile, fileMetadataList);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Throwable innerException = checkForAzureStorageException(e);
|
||||||
|
|
||||||
|
if (innerException instanceof StorageException
|
||||||
|
&& isFileNotFoundException((StorageException) innerException)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
long end = Time.monotonicNow();
|
||||||
|
LOG.debug("Time taken to list {} blobs for delete operation: {} ms",
|
||||||
|
fileMetadataList.size(), (end - start));
|
||||||
|
|
||||||
|
// Here contents holds the list of metadata of the files and folders that can be deleted
|
||||||
|
// under the path that is requested for delete(excluding itself).
|
||||||
|
final FileMetadata[] contents = fileMetadataList.toArray(new FileMetadata[fileMetadataList.size()]);
|
||||||
|
|
||||||
|
if (contents.length > 0 && !recursive) {
|
||||||
|
// The folder is non-empty and recursive delete was not specified.
|
||||||
|
// Throw an exception indicating that a non-recursive delete was
|
||||||
|
// specified for a non-empty folder.
|
||||||
|
throw new IOException("Non-recursive delete of non-empty directory "
|
||||||
|
+ f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete all files / folders in current directory stored as list in 'contents'.
|
||||||
|
AzureFileSystemThreadTask task = new AzureFileSystemThreadTask() {
|
||||||
|
@Override
|
||||||
|
public boolean execute(FileMetadata file) throws IOException{
|
||||||
|
if (!deleteFile(file.getKey(), file.isDir())) {
|
||||||
|
LOG.warn("Attempt to delete non-existent {} {}",
|
||||||
|
file.isDir() ? "directory" : "file",
|
||||||
|
file.getKey());
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
AzureFileSystemThreadPoolExecutor executor = getThreadPoolExecutor(this.deleteThreadCount,
|
||||||
|
"AzureBlobDeleteThread", "Delete", key, AZURE_DELETE_THREADS);
|
||||||
|
|
||||||
|
if (!executor.executeParallel(contents, task)) {
|
||||||
|
LOG.error("Failed to delete files / subfolders in blob {}", key);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (metaFile.getKey().equals("/")) {
|
||||||
|
LOG.error("Cannot delete root directory {}", f);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the current directory if all underlying contents are deleted
|
||||||
|
if (isPartialDelete || (store.retrieveMetadata(metaFile.getKey()) != null
|
||||||
|
&& !deleteFile(metaFile.getKey(), metaFile.isDir()))) {
|
||||||
|
LOG.error("Failed delete directory : {}", f);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update parent directory last modified time
|
||||||
|
Path parent = absolutePath.getParent();
|
||||||
|
if (parent != null && parent.getParent() != null) { // not root
|
||||||
|
if (!skipParentFolderLastModifiedTimeUpdate) {
|
||||||
|
updateParentFolderLastModifiedTime(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// File or directory was successfully deleted.
|
||||||
|
LOG.debug("Delete Successful for : {}", f);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean deleteWithoutAuth(Path f, boolean recursive,
|
||||||
|
boolean skipParentFolderLastModifiedTimeUpdate) throws IOException {
|
||||||
|
|
||||||
|
LOG.debug("Deleting file: {}", f);
|
||||||
|
|
||||||
|
Path absolutePath = makeAbsolute(f);
|
||||||
|
Path parentPath = absolutePath.getParent();
|
||||||
|
|
||||||
String key = pathToKey(absolutePath);
|
String key = pathToKey(absolutePath);
|
||||||
|
|
||||||
|
@ -1884,10 +2113,10 @@ public class NativeAzureFileSystem extends FileSystem {
|
||||||
metaFile = store.retrieveMetadata(key);
|
metaFile = store.retrieveMetadata(key);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
|
||||||
Throwable innerException = NativeAzureFileSystemHelper.checkForAzureStorageException(e);
|
Throwable innerException = checkForAzureStorageException(e);
|
||||||
|
|
||||||
if (innerException instanceof StorageException
|
if (innerException instanceof StorageException
|
||||||
&& NativeAzureFileSystemHelper.isFileNotFoundException((StorageException) innerException)) {
|
&& isFileNotFoundException((StorageException) innerException)) {
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -1918,7 +2147,7 @@ public class NativeAzureFileSystem extends FileSystem {
|
||||||
parentMetadata = store.retrieveMetadata(parentKey);
|
parentMetadata = store.retrieveMetadata(parentKey);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
|
||||||
Throwable innerException = NativeAzureFileSystemHelper.checkForAzureStorageException(e);
|
Throwable innerException = checkForAzureStorageException(e);
|
||||||
|
|
||||||
if (innerException instanceof StorageException) {
|
if (innerException instanceof StorageException) {
|
||||||
// Invalid State.
|
// Invalid State.
|
||||||
|
@ -1926,7 +2155,7 @@ public class NativeAzureFileSystem extends FileSystem {
|
||||||
// if the file not present. But not retrieving metadata here is an
|
// if the file not present. But not retrieving metadata here is an
|
||||||
// unrecoverable state and can only happen if there is a race condition
|
// unrecoverable state and can only happen if there is a race condition
|
||||||
// hence throwing a IOException
|
// hence throwing a IOException
|
||||||
if (NativeAzureFileSystemHelper.isFileNotFoundException((StorageException) innerException)) {
|
if (isFileNotFoundException((StorageException) innerException)) {
|
||||||
throw new IOException("File " + f + " has a parent directory "
|
throw new IOException("File " + f + " has a parent directory "
|
||||||
+ parentPath + " whose metadata cannot be retrieved. Can't resolve");
|
+ parentPath + " whose metadata cannot be retrieved. Can't resolve");
|
||||||
}
|
}
|
||||||
|
@ -1972,10 +2201,10 @@ public class NativeAzureFileSystem extends FileSystem {
|
||||||
}
|
}
|
||||||
} catch(IOException e) {
|
} catch(IOException e) {
|
||||||
|
|
||||||
Throwable innerException = NativeAzureFileSystemHelper.checkForAzureStorageException(e);
|
Throwable innerException = checkForAzureStorageException(e);
|
||||||
|
|
||||||
if (innerException instanceof StorageException
|
if (innerException instanceof StorageException
|
||||||
&& NativeAzureFileSystemHelper.isFileNotFoundException((StorageException) innerException)) {
|
&& isFileNotFoundException((StorageException) innerException)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1984,7 +2213,7 @@ public class NativeAzureFileSystem extends FileSystem {
|
||||||
} else {
|
} else {
|
||||||
// The path specifies a folder. Recursively delete all entries under the
|
// The path specifies a folder. Recursively delete all entries under the
|
||||||
// folder.
|
// folder.
|
||||||
LOG.debug("Directory Delete encountered: {}", f.toString());
|
LOG.debug("Directory Delete encountered: {}", f);
|
||||||
if (parentPath.getParent() != null) {
|
if (parentPath.getParent() != null) {
|
||||||
String parentKey = pathToKey(parentPath);
|
String parentKey = pathToKey(parentPath);
|
||||||
FileMetadata parentMetadata = null;
|
FileMetadata parentMetadata = null;
|
||||||
|
@ -1993,7 +2222,7 @@ public class NativeAzureFileSystem extends FileSystem {
|
||||||
parentMetadata = store.retrieveMetadata(parentKey);
|
parentMetadata = store.retrieveMetadata(parentKey);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
|
||||||
Throwable innerException = NativeAzureFileSystemHelper.checkForAzureStorageException(e);
|
Throwable innerException = checkForAzureStorageException(e);
|
||||||
|
|
||||||
if (innerException instanceof StorageException) {
|
if (innerException instanceof StorageException) {
|
||||||
// Invalid State.
|
// Invalid State.
|
||||||
|
@ -2001,7 +2230,7 @@ public class NativeAzureFileSystem extends FileSystem {
|
||||||
// if the file not present. But not retrieving metadata here is an
|
// if the file not present. But not retrieving metadata here is an
|
||||||
// unrecoverable state and can only happen if there is a race condition
|
// unrecoverable state and can only happen if there is a race condition
|
||||||
// hence throwing a IOException
|
// hence throwing a IOException
|
||||||
if (NativeAzureFileSystemHelper.isFileNotFoundException((StorageException) innerException)) {
|
if (isFileNotFoundException((StorageException) innerException)) {
|
||||||
throw new IOException("File " + f + " has a parent directory "
|
throw new IOException("File " + f + " has a parent directory "
|
||||||
+ parentPath + " whose metadata cannot be retrieved. Can't resolve");
|
+ parentPath + " whose metadata cannot be retrieved. Can't resolve");
|
||||||
}
|
}
|
||||||
|
@ -2046,10 +2275,10 @@ public class NativeAzureFileSystem extends FileSystem {
|
||||||
}
|
}
|
||||||
priorLastKey = listing.getPriorLastKey();
|
priorLastKey = listing.getPriorLastKey();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Throwable innerException = NativeAzureFileSystemHelper.checkForAzureStorageException(e);
|
Throwable innerException = checkForAzureStorageException(e);
|
||||||
|
|
||||||
if (innerException instanceof StorageException
|
if (innerException instanceof StorageException
|
||||||
&& NativeAzureFileSystemHelper.isFileNotFoundException((StorageException) innerException)) {
|
&& isFileNotFoundException((StorageException) innerException)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2067,22 +2296,7 @@ public class NativeAzureFileSystem extends FileSystem {
|
||||||
// The folder is non-empty and recursive delete was not specified.
|
// The folder is non-empty and recursive delete was not specified.
|
||||||
// Throw an exception indicating that a non-recursive delete was
|
// Throw an exception indicating that a non-recursive delete was
|
||||||
// specified for a non-empty folder.
|
// specified for a non-empty folder.
|
||||||
throw new IOException("Non-recursive delete of non-empty directory "
|
throw new IOException("Non-recursive delete of non-empty directory "+ f);
|
||||||
+ f.toString());
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Check write-permissions on sub-tree including current folder
|
|
||||||
// NOTE: Ideally the subtree needs read-write-execute access check.
|
|
||||||
// But we will simplify it to write-access check.
|
|
||||||
if (metaFile.isDir()) { // the absolute-path
|
|
||||||
performAuthCheck(absolutePath, WasbAuthorizationOperations.WRITE, "delete", absolutePath);
|
|
||||||
}
|
|
||||||
for (FileMetadata meta : contents) {
|
|
||||||
if (meta.isDir()) {
|
|
||||||
Path subTreeDir = keyToPath(meta.getKey());
|
|
||||||
performAuthCheck(subTreeDir, WasbAuthorizationOperations.WRITE, "delete", absolutePath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2108,8 +2322,9 @@ public class NativeAzureFileSystem extends FileSystem {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete the current directory
|
// Delete the current directory
|
||||||
if (store.retrieveMetadata(metaFile.getKey()) != null && !deleteFile(metaFile.getKey(), metaFile.isDir())) {
|
if (store.retrieveMetadata(metaFile.getKey()) != null
|
||||||
LOG.error("Failed delete directory {}", f.toString());
|
&& !deleteFile(metaFile.getKey(), metaFile.isDir())) {
|
||||||
|
LOG.error("Failed delete directory : {}", f);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2123,15 +2338,226 @@ public class NativeAzureFileSystem extends FileSystem {
|
||||||
}
|
}
|
||||||
|
|
||||||
// File or directory was successfully deleted.
|
// File or directory was successfully deleted.
|
||||||
LOG.debug("Delete Successful for : {}", f.toString());
|
LOG.debug("Delete Successful for : {}", f);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the specified file or folder. The parameter
|
||||||
|
* skipParentFolderLastModifiedTimeUpdate
|
||||||
|
* is used in the case of atomic folder rename redo. In that case, there is
|
||||||
|
* a lease on the parent folder, so (without reworking the code) modifying
|
||||||
|
* the parent folder update time will fail because of a conflict with the
|
||||||
|
* lease. Since we are going to delete the folder soon anyway so accurate
|
||||||
|
* modified time is not necessary, it's easier to just skip
|
||||||
|
* the modified time update.
|
||||||
|
*
|
||||||
|
* @param f file path to be deleted.
|
||||||
|
* @param recursive specify deleting recursively or not.
|
||||||
|
* @param skipParentFolderLastModifiedTimeUpdate If true, don't update the folder last
|
||||||
|
* modified time.
|
||||||
|
* @return true if and only if the file is deleted
|
||||||
|
* @throws IOException Thrown when fail to delete file or directory.
|
||||||
|
*/
|
||||||
|
public boolean delete(Path f, boolean recursive,
|
||||||
|
boolean skipParentFolderLastModifiedTimeUpdate) throws IOException {
|
||||||
|
|
||||||
|
if (this.azureAuthorization) {
|
||||||
|
return deleteWithAuthEnabled(f, recursive,
|
||||||
|
skipParentFolderLastModifiedTimeUpdate);
|
||||||
|
} else {
|
||||||
|
return deleteWithoutAuth(f, recursive,
|
||||||
|
skipParentFolderLastModifiedTimeUpdate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public AzureFileSystemThreadPoolExecutor getThreadPoolExecutor(int threadCount,
|
public AzureFileSystemThreadPoolExecutor getThreadPoolExecutor(int threadCount,
|
||||||
String threadNamePrefix, String operation, String key, String config) {
|
String threadNamePrefix, String operation, String key, String config) {
|
||||||
return new AzureFileSystemThreadPoolExecutor(threadCount, threadNamePrefix, operation, key, config);
|
return new AzureFileSystemThreadPoolExecutor(threadCount, threadNamePrefix, operation, key, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets list of contents that can be deleted based on authorization check calls
|
||||||
|
* performed on the sub-tree for the folderToDelete.
|
||||||
|
*
|
||||||
|
* @param folderToDelete - metadata of the folder whose delete is requested.
|
||||||
|
* @param finalList - list of metadata of all files/folders that can be deleted .
|
||||||
|
*
|
||||||
|
* @return 'true' only if all the contents of the folderToDelete can be deleted
|
||||||
|
* @throws IOException Thrown when current user cannot be retrieved.
|
||||||
|
*/
|
||||||
|
private boolean getFolderContentsToDelete(FileMetadata folderToDelete,
|
||||||
|
ArrayList<FileMetadata> finalList) throws IOException {
|
||||||
|
|
||||||
|
final int maxListingDepth = 1;
|
||||||
|
Stack<FileMetadata> foldersToProcess = new Stack<FileMetadata>();
|
||||||
|
HashMap<String, FileMetadata> folderContentsMap = new HashMap<String, FileMetadata>();
|
||||||
|
|
||||||
|
boolean isPartialDelete = false;
|
||||||
|
|
||||||
|
Path pathToDelete = makeAbsolute(keyToPath(folderToDelete.getKey()));
|
||||||
|
foldersToProcess.push(folderToDelete);
|
||||||
|
|
||||||
|
while (!foldersToProcess.empty()) {
|
||||||
|
|
||||||
|
FileMetadata currentFolder = foldersToProcess.pop();
|
||||||
|
Path currentPath = makeAbsolute(keyToPath(currentFolder.getKey()));
|
||||||
|
boolean canDeleteChildren = true;
|
||||||
|
|
||||||
|
// If authorization is enabled, check for 'write' permission on current folder
|
||||||
|
// This check maps to subfolders 'write' check for deleting contents recursively.
|
||||||
|
try {
|
||||||
|
performAuthCheck(currentPath, WasbAuthorizationOperations.WRITE, "delete", pathToDelete);
|
||||||
|
} catch (WasbAuthorizationException we) {
|
||||||
|
LOG.debug("Authorization check failed for {}", currentPath);
|
||||||
|
// We cannot delete the children of currentFolder since 'write' check on parent failed
|
||||||
|
canDeleteChildren = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (canDeleteChildren) {
|
||||||
|
|
||||||
|
// get immediate children list
|
||||||
|
ArrayList<FileMetadata> fileMetadataList = getChildrenMetadata(currentFolder.getKey(),
|
||||||
|
maxListingDepth);
|
||||||
|
|
||||||
|
// Process children of currentFolder and add them to list of contents
|
||||||
|
// that can be deleted. We Perform stickybit check on every file and
|
||||||
|
// folder under currentFolder in case stickybit is set on currentFolder.
|
||||||
|
for (FileMetadata childItem : fileMetadataList) {
|
||||||
|
if (isStickyBitCheckViolated(childItem, currentFolder, false)) {
|
||||||
|
// Stickybit check failed for the childItem that is being processed.
|
||||||
|
// This file/folder cannot be deleted and neither can the parent paths be deleted.
|
||||||
|
// Remove parent paths from list of contents that can be deleted.
|
||||||
|
canDeleteChildren = false;
|
||||||
|
Path filePath = makeAbsolute(keyToPath(childItem.getKey()));
|
||||||
|
LOG.error("User does not have permissions to delete {}. "
|
||||||
|
+ "Parent directory has sticky bit set.", filePath);
|
||||||
|
} else {
|
||||||
|
// push the child directories to the stack to process their contents
|
||||||
|
if (childItem.isDir()) {
|
||||||
|
foldersToProcess.push(childItem);
|
||||||
|
}
|
||||||
|
// Add items to list of contents that can be deleted.
|
||||||
|
folderContentsMap.put(childItem.getKey(), childItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Cannot delete children since parent permission check has not passed and
|
||||||
|
// if there are files/folders under currentFolder they will not be deleted.
|
||||||
|
LOG.error("Authorization check failed. Files or folders under {} "
|
||||||
|
+ "will not be processed for deletion.", currentPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!canDeleteChildren) {
|
||||||
|
// We reach here if
|
||||||
|
// 1. cannot delete children since 'write' check on parent failed or
|
||||||
|
// 2. One of the files under the current folder cannot be deleted due to stickybit check.
|
||||||
|
// In this case we remove all the parent paths from the list of contents
|
||||||
|
// that can be deleted till we reach the original path of delete request
|
||||||
|
String pathToRemove = currentFolder.getKey();
|
||||||
|
while (!pathToRemove.equals(folderToDelete.getKey())) {
|
||||||
|
if (folderContentsMap.containsKey(pathToRemove)) {
|
||||||
|
LOG.debug("Cannot delete {} since some of its contents "
|
||||||
|
+ "cannot be deleted", pathToRemove);
|
||||||
|
folderContentsMap.remove(pathToRemove);
|
||||||
|
}
|
||||||
|
Path parentPath = keyToPath(pathToRemove).getParent();
|
||||||
|
pathToRemove = pathToKey(parentPath);
|
||||||
|
}
|
||||||
|
// Since one or more files/folders cannot be deleted return value should indicate
|
||||||
|
// partial delete, so that the delete on the path requested by user is not performed
|
||||||
|
isPartialDelete = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// final list of contents that can be deleted
|
||||||
|
for (HashMap.Entry<String, FileMetadata> entry : folderContentsMap.entrySet()) {
|
||||||
|
finalList.add(entry.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
return isPartialDelete;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ArrayList<FileMetadata> getChildrenMetadata(String key, int maxListingDepth)
|
||||||
|
throws IOException {
|
||||||
|
|
||||||
|
String priorLastKey = null;
|
||||||
|
ArrayList<FileMetadata> fileMetadataList = new ArrayList<FileMetadata>();
|
||||||
|
do {
|
||||||
|
PartialListing listing = store.listAll(key, AZURE_LIST_ALL,
|
||||||
|
maxListingDepth, priorLastKey);
|
||||||
|
for (FileMetadata file : listing.getFiles()) {
|
||||||
|
fileMetadataList.add(file);
|
||||||
|
}
|
||||||
|
priorLastKey = listing.getPriorLastKey();
|
||||||
|
} while (priorLastKey != null);
|
||||||
|
|
||||||
|
return fileMetadataList;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isStickyBitCheckViolated(FileMetadata metaData,
|
||||||
|
FileMetadata parentMetadata, boolean throwOnException) throws IOException {
|
||||||
|
try {
|
||||||
|
return isStickyBitCheckViolated(metaData, parentMetadata);
|
||||||
|
} catch (FileNotFoundException ex) {
|
||||||
|
if (throwOnException) {
|
||||||
|
throw ex;
|
||||||
|
} else {
|
||||||
|
LOG.debug("Encountered FileNotFoundException while performing "
|
||||||
|
+ "stickybit check operation for {}", metaData.getKey());
|
||||||
|
// swallow exception and return that stickyBit check has been violated
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the Current user is not permitted access to a file/folder when
|
||||||
|
* sticky bit is set on parent path. Only the owner of parent path
|
||||||
|
* and owner of the file/folder itself are permitted to perform certain
|
||||||
|
* operations on file/folder based on sticky bit check. Sticky bit check will
|
||||||
|
* be performed only when authorization is enabled.
|
||||||
|
*
|
||||||
|
* @param metaData - metadata of the file/folder whose parent has sticky bit set.
|
||||||
|
* @param parentMetadata - metadata of the parent.
|
||||||
|
*
|
||||||
|
* @return true if Current user violates stickybit check
|
||||||
|
* @throws IOException Thrown when current user cannot be retrieved.
|
||||||
|
*/
|
||||||
|
private boolean isStickyBitCheckViolated(FileMetadata metaData,
|
||||||
|
FileMetadata parentMetadata) throws IOException {
|
||||||
|
|
||||||
|
// In case stickybit check should not be performed,
|
||||||
|
// return value should indicate stickybit check is not violated.
|
||||||
|
if (!this.azureAuthorization) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This should never happen when the sticky bit check is invoked.
|
||||||
|
if (parentMetadata == null) {
|
||||||
|
throw new FileNotFoundException(
|
||||||
|
String.format("Parent metadata for '%s' not found!", metaData.getKey()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// stickybit is not set on parent and hence cannot be violated
|
||||||
|
if (!parentMetadata.getPermissionStatus().getPermission().getStickyBit()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String currentUser = UserGroupInformation.getCurrentUser().getShortUserName();
|
||||||
|
String parentDirectoryOwner = parentMetadata.getPermissionStatus().getUserName();
|
||||||
|
String currentFileOwner = metaData.getPermissionStatus().getUserName();
|
||||||
|
|
||||||
|
// Files/Folders with no owner set will not pass stickybit check
|
||||||
|
if ((parentDirectoryOwner.equalsIgnoreCase(currentUser))
|
||||||
|
|| currentFileOwner.equalsIgnoreCase(currentUser)) {
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete the specified file or directory and increment metrics.
|
* Delete the specified file or directory and increment metrics.
|
||||||
* If the file or directory does not exist, the operation returns false.
|
* If the file or directory does not exist, the operation returns false.
|
||||||
|
|
|
@ -27,7 +27,7 @@ import static org.apache.hadoop.fs.azure.SecureStorageInterfaceImpl.KEY_USE_CONT
|
||||||
* to access storage.
|
* to access storage.
|
||||||
*/
|
*/
|
||||||
public class ITestNativeAzureFSAuthWithBlobSpecificKeys
|
public class ITestNativeAzureFSAuthWithBlobSpecificKeys
|
||||||
extends ITestNativeAzureFileSystemAuthorizationWithOwner {
|
extends TestNativeAzureFileSystemAuthorization {
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -27,7 +27,7 @@ import static org.apache.hadoop.fs.azure.CachingAuthorizer.KEY_AUTH_SERVICE_CACH
|
||||||
* Test class to hold all WASB authorization caching related tests.
|
* Test class to hold all WASB authorization caching related tests.
|
||||||
*/
|
*/
|
||||||
public class ITestNativeAzureFSAuthorizationCaching
|
public class ITestNativeAzureFSAuthorizationCaching
|
||||||
extends ITestNativeAzureFileSystemAuthorizationWithOwner {
|
extends TestNativeAzureFileSystemAuthorization {
|
||||||
|
|
||||||
private static final int DUMMY_TTL_VALUE = 5000;
|
private static final int DUMMY_TTL_VALUE = 5000;
|
||||||
|
|
||||||
|
|
|
@ -1,122 +0,0 @@
|
||||||
/**
|
|
||||||
* 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.hadoop.fs.azure;
|
|
||||||
|
|
||||||
import org.apache.hadoop.fs.contract.ContractTestUtils;
|
|
||||||
import org.apache.hadoop.security.UserGroupInformation;
|
|
||||||
import java.security.PrivilegedExceptionAction;
|
|
||||||
|
|
||||||
import org.apache.hadoop.fs.Path;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test class that runs wasb authorization tests with owner check enabled.
|
|
||||||
*/
|
|
||||||
public class ITestNativeAzureFileSystemAuthorizationWithOwner
|
|
||||||
extends TestNativeAzureFileSystemAuthorization {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setUp() throws Exception {
|
|
||||||
super.setUp();
|
|
||||||
authorizer.init(fs.getConf(), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test case when owner matches current user.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testOwnerPermissionPositive() throws Throwable {
|
|
||||||
|
|
||||||
Path parentDir = new Path("/testOwnerPermissionPositive");
|
|
||||||
Path testPath = new Path(parentDir, "test.data");
|
|
||||||
|
|
||||||
authorizer.addAuthRule("/", WasbAuthorizationOperations.WRITE.toString(), true);
|
|
||||||
authorizer.addAuthRule(testPath.toString(), WasbAuthorizationOperations.READ.toString(), true);
|
|
||||||
authorizer.addAuthRule(parentDir.toString(), WasbAuthorizationOperations.WRITE.toString(), true);
|
|
||||||
// additional rule used for assertPathExists
|
|
||||||
authorizer.addAuthRule(parentDir.toString(), WasbAuthorizationOperations.READ.toString(), true);
|
|
||||||
fs.updateWasbAuthorizer(authorizer);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// creates parentDir with owner as current user
|
|
||||||
fs.mkdirs(parentDir);
|
|
||||||
ContractTestUtils.assertPathExists(fs, "parentDir does not exist", parentDir);
|
|
||||||
|
|
||||||
fs.create(testPath);
|
|
||||||
fs.getFileStatus(testPath);
|
|
||||||
ContractTestUtils.assertPathExists(fs, "testPath does not exist", testPath);
|
|
||||||
|
|
||||||
} finally {
|
|
||||||
allowRecursiveDelete(fs, parentDir.toString());
|
|
||||||
fs.delete(parentDir, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Negative test case for owner does not match current user.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testOwnerPermissionNegative() throws Throwable {
|
|
||||||
expectedEx.expect(WasbAuthorizationException.class);
|
|
||||||
|
|
||||||
Path parentDir = new Path("/testOwnerPermissionNegative");
|
|
||||||
Path childDir = new Path(parentDir, "childDir");
|
|
||||||
|
|
||||||
setExpectedFailureMessage("mkdirs", childDir);
|
|
||||||
|
|
||||||
authorizer.addAuthRule("/", WasbAuthorizationOperations.WRITE.toString(), true);
|
|
||||||
authorizer.addAuthRule(parentDir.toString(), WasbAuthorizationOperations.WRITE.toString(), true);
|
|
||||||
|
|
||||||
fs.updateWasbAuthorizer(authorizer);
|
|
||||||
|
|
||||||
try{
|
|
||||||
fs.mkdirs(parentDir);
|
|
||||||
UserGroupInformation ugiSuperUser = UserGroupInformation.createUserForTesting(
|
|
||||||
"testuser", new String[] {});
|
|
||||||
|
|
||||||
ugiSuperUser.doAs(new PrivilegedExceptionAction<Void>() {
|
|
||||||
@Override
|
|
||||||
public Void run() throws Exception {
|
|
||||||
fs.mkdirs(childDir);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
} finally {
|
|
||||||
allowRecursiveDelete(fs, parentDir.toString());
|
|
||||||
fs.delete(parentDir, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test to verify that retrieving owner information does not
|
|
||||||
* throw when file/folder does not exist.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testRetrievingOwnerDoesNotFailWhenFileDoesNotExist() throws Throwable {
|
|
||||||
|
|
||||||
Path testdirectory = new Path("/testDirectory123454565");
|
|
||||||
|
|
||||||
String owner = fs.getOwnerForPath(testdirectory);
|
|
||||||
assertEquals("", owner);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -34,165 +34,183 @@ import org.apache.hadoop.fs.Path;
|
||||||
|
|
||||||
public class MockWasbAuthorizerImpl implements WasbAuthorizerInterface {
|
public class MockWasbAuthorizerImpl implements WasbAuthorizerInterface {
|
||||||
|
|
||||||
private Map<AuthorizationComponent, Boolean> authRules;
|
private Map<AuthorizationComponent, Boolean> authRules;
|
||||||
private boolean performOwnerMatch;
|
private CachingAuthorizer<CachedAuthorizerEntry, Boolean> cache;
|
||||||
private CachingAuthorizer<CachedAuthorizerEntry, Boolean> cache;
|
|
||||||
|
|
||||||
// The full qualified URL to the root directory
|
// The full qualified URL to the root directory
|
||||||
private String qualifiedPrefixUrl;
|
private String qualifiedPrefixUrl;
|
||||||
|
|
||||||
public MockWasbAuthorizerImpl(NativeAzureFileSystem fs) {
|
public MockWasbAuthorizerImpl(NativeAzureFileSystem fs) {
|
||||||
qualifiedPrefixUrl = new Path("/").makeQualified(fs.getUri(),
|
qualifiedPrefixUrl = new Path("/").makeQualified(fs.getUri(),
|
||||||
fs.getWorkingDirectory())
|
fs.getWorkingDirectory())
|
||||||
.toString().replaceAll("/$", "");
|
.toString().replaceAll("/$", "");
|
||||||
cache = new CachingAuthorizer<>(TimeUnit.MINUTES.convert(5L, TimeUnit.MINUTES), "AUTHORIZATION");
|
cache = new CachingAuthorizer<>(TimeUnit.MINUTES.convert(5L, TimeUnit.MINUTES), "AUTHORIZATION");
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void init(Configuration conf) {
|
|
||||||
init(conf, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
authorization matches owner with currentUserShortName while evaluating auth rules
|
|
||||||
if currentUserShortName is set to a string that is not empty
|
|
||||||
*/
|
|
||||||
public void init(Configuration conf, boolean matchOwner) {
|
|
||||||
cache.init(conf);
|
|
||||||
authRules = new HashMap<>();
|
|
||||||
this.performOwnerMatch = matchOwner;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addAuthRule(String wasbAbsolutePath,
|
|
||||||
String accessType, boolean access) {
|
|
||||||
wasbAbsolutePath = qualifiedPrefixUrl + wasbAbsolutePath;
|
|
||||||
AuthorizationComponent component = wasbAbsolutePath.endsWith("*")
|
|
||||||
? new AuthorizationComponent("^" + wasbAbsolutePath.replace("*", ".*"),
|
|
||||||
accessType)
|
|
||||||
: new AuthorizationComponent(wasbAbsolutePath, accessType);
|
|
||||||
|
|
||||||
this.authRules.put(component, access);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean authorize(String wasbAbsolutePath,
|
|
||||||
String accessType,
|
|
||||||
String owner)
|
|
||||||
throws WasbAuthorizationException {
|
|
||||||
|
|
||||||
if (wasbAbsolutePath.endsWith(
|
|
||||||
NativeAzureFileSystem.FolderRenamePending.SUFFIX)) {
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CachedAuthorizerEntry cacheKey = new CachedAuthorizerEntry(wasbAbsolutePath, accessType, owner);
|
@Override
|
||||||
Boolean cacheresult = cache.get(cacheKey);
|
public void init(Configuration conf) {
|
||||||
if (cacheresult != null) {
|
cache.init(conf);
|
||||||
return cacheresult;
|
authRules = new HashMap<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean authorizeresult = authorizeInternal(wasbAbsolutePath, accessType, owner);
|
public void addAuthRuleForOwner(String wasbAbsolutePath,
|
||||||
cache.put(cacheKey, authorizeresult);
|
String accessType, boolean access) {
|
||||||
|
addAuthRule(wasbAbsolutePath, accessType, "owner", access);
|
||||||
return authorizeresult;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean authorizeInternal(String wasbAbsolutePath, String accessType, String owner)
|
|
||||||
throws WasbAuthorizationException {
|
|
||||||
|
|
||||||
String currentUserShortName = "";
|
|
||||||
if (this.performOwnerMatch) {
|
|
||||||
try {
|
|
||||||
UserGroupInformation ugi = UserGroupInformation.getCurrentUser();
|
|
||||||
currentUserShortName = ugi.getShortUserName();
|
|
||||||
} catch (Exception e) {
|
|
||||||
//no op
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// In case of root("/"), owner match does not happen because owner is returned as empty string.
|
public void addAuthRule(String wasbAbsolutePath,
|
||||||
// we try to force owner match just for purpose of tests to make sure all operations work seemlessly with owner.
|
String accessType, String user, boolean access) {
|
||||||
if (this.performOwnerMatch
|
wasbAbsolutePath = qualifiedPrefixUrl + wasbAbsolutePath;
|
||||||
&& StringUtils.equalsIgnoreCase(wasbAbsolutePath,
|
AuthorizationComponent component = wasbAbsolutePath.endsWith("*")
|
||||||
qualifiedPrefixUrl + "/")) {
|
? new AuthorizationComponent("^" + wasbAbsolutePath.replace("*", ".*"),
|
||||||
owner = currentUserShortName;
|
accessType, user)
|
||||||
|
: new AuthorizationComponent(wasbAbsolutePath, accessType, user);
|
||||||
|
|
||||||
|
this.authRules.put(component, access);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean shouldEvaluateOwnerAccess = owner != null && !owner.isEmpty()
|
@Override
|
||||||
&& this.performOwnerMatch;
|
public boolean authorize(String wasbAbsolutePath,
|
||||||
|
String accessType,
|
||||||
|
String owner)
|
||||||
|
throws WasbAuthorizationException {
|
||||||
|
|
||||||
boolean isOwnerMatch = StringUtils.equalsIgnoreCase(currentUserShortName,
|
if (wasbAbsolutePath.endsWith(
|
||||||
owner);
|
NativeAzureFileSystem.FolderRenamePending.SUFFIX)) {
|
||||||
|
return true;
|
||||||
AuthorizationComponent component =
|
|
||||||
new AuthorizationComponent(wasbAbsolutePath, accessType);
|
|
||||||
|
|
||||||
if (authRules.containsKey(component)) {
|
|
||||||
return shouldEvaluateOwnerAccess ? isOwnerMatch && authRules.get(
|
|
||||||
component) : authRules.get(component);
|
|
||||||
} else {
|
|
||||||
// Regex-pattern match if we don't have a straight match
|
|
||||||
for (Map.Entry<AuthorizationComponent, Boolean> entry : authRules.entrySet()) {
|
|
||||||
AuthorizationComponent key = entry.getKey();
|
|
||||||
String keyPath = key.getWasbAbsolutePath();
|
|
||||||
String keyAccess = key.getAccessType();
|
|
||||||
|
|
||||||
if (keyPath.endsWith("*") && Pattern.matches(keyPath, wasbAbsolutePath)
|
|
||||||
&& keyAccess.equals(accessType)) {
|
|
||||||
return shouldEvaluateOwnerAccess
|
|
||||||
? isOwnerMatch && entry.getValue()
|
|
||||||
: entry.getValue();
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void deleteAllAuthRules() {
|
CachedAuthorizerEntry cacheKey = new CachedAuthorizerEntry(wasbAbsolutePath, accessType, owner);
|
||||||
authRules.clear();
|
Boolean cacheresult = cache.get(cacheKey);
|
||||||
cache.clear();
|
if (cacheresult != null) {
|
||||||
}
|
return cacheresult;
|
||||||
|
}
|
||||||
|
|
||||||
private static class AuthorizationComponent {
|
boolean authorizeresult = authorizeInternal(wasbAbsolutePath, accessType, owner);
|
||||||
|
cache.put(cacheKey, authorizeresult);
|
||||||
|
|
||||||
private final String wasbAbsolutePath;
|
return authorizeresult;
|
||||||
private final String accessType;
|
|
||||||
|
|
||||||
AuthorizationComponent(String wasbAbsolutePath,
|
|
||||||
String accessType) {
|
|
||||||
this.wasbAbsolutePath = wasbAbsolutePath;
|
|
||||||
this.accessType = accessType;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private boolean authorizeInternal(String wasbAbsolutePath, String accessType, String owner)
|
||||||
public int hashCode() {
|
throws WasbAuthorizationException {
|
||||||
return this.wasbAbsolutePath.hashCode() ^ this.accessType.hashCode();
|
|
||||||
|
String currentUserShortName = "";
|
||||||
|
try {
|
||||||
|
UserGroupInformation ugi = UserGroupInformation.getCurrentUser();
|
||||||
|
currentUserShortName = ugi.getShortUserName();
|
||||||
|
} catch (Exception e) {
|
||||||
|
//no op
|
||||||
|
}
|
||||||
|
|
||||||
|
// In case of root("/"), owner match does not happen
|
||||||
|
// because owner is returned as empty string.
|
||||||
|
// we try to force owner match just for purpose of tests
|
||||||
|
// to make sure all operations work seemlessly with owner.
|
||||||
|
if (StringUtils.equalsIgnoreCase(wasbAbsolutePath, qualifiedPrefixUrl + "/")) {
|
||||||
|
owner = currentUserShortName;
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthorizationComponent component = new AuthorizationComponent(wasbAbsolutePath,
|
||||||
|
accessType, currentUserShortName);
|
||||||
|
|
||||||
|
return processRules(authRules, component, owner);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private boolean processRules(Map<AuthorizationComponent, Boolean> authRules,
|
||||||
public boolean equals(Object obj) {
|
AuthorizationComponent component, String owner) {
|
||||||
|
|
||||||
if (obj == this) {
|
// Direct match of rules and access request
|
||||||
return true;
|
if (authRules.containsKey(component)) {
|
||||||
|
return authRules.get(component);
|
||||||
|
} else {
|
||||||
|
// Regex-pattern match if we don't have a straight match for path
|
||||||
|
// Current user match if we don't have a owner match
|
||||||
|
for (Map.Entry<AuthorizationComponent, Boolean> entry : authRules.entrySet()) {
|
||||||
|
AuthorizationComponent key = entry.getKey();
|
||||||
|
String keyPath = key.getWasbAbsolutePath();
|
||||||
|
String keyAccess = key.getAccessType();
|
||||||
|
String keyUser = key.getUser();
|
||||||
|
|
||||||
|
boolean foundMatchingOwnerRule = keyPath.equals(component.getWasbAbsolutePath())
|
||||||
|
&& keyAccess.equals(component.getAccessType())
|
||||||
|
&& keyUser.equalsIgnoreCase("owner")
|
||||||
|
&& owner.equals(component.getUser());
|
||||||
|
|
||||||
|
boolean foundMatchingPatternRule = keyPath.endsWith("*")
|
||||||
|
&& Pattern.matches(keyPath, component.getWasbAbsolutePath())
|
||||||
|
&& keyAccess.equals(component.getAccessType())
|
||||||
|
&& keyUser.equalsIgnoreCase(component.getUser());
|
||||||
|
|
||||||
|
boolean foundMatchingPatternOwnerRule = keyPath.endsWith("*")
|
||||||
|
&& Pattern.matches(keyPath, component.getWasbAbsolutePath())
|
||||||
|
&& keyAccess.equals(component.getAccessType())
|
||||||
|
&& keyUser.equalsIgnoreCase("owner")
|
||||||
|
&& owner.equals(component.getUser());
|
||||||
|
|
||||||
|
if (foundMatchingOwnerRule
|
||||||
|
|| foundMatchingPatternRule
|
||||||
|
|| foundMatchingPatternOwnerRule) {
|
||||||
|
return entry.getValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteAllAuthRules() {
|
||||||
|
authRules.clear();
|
||||||
|
cache.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class AuthorizationComponent {
|
||||||
|
|
||||||
|
private final String wasbAbsolutePath;
|
||||||
|
private final String accessType;
|
||||||
|
private final String user;
|
||||||
|
|
||||||
|
AuthorizationComponent(String wasbAbsolutePath,
|
||||||
|
String accessType, String user) {
|
||||||
|
this.wasbAbsolutePath = wasbAbsolutePath;
|
||||||
|
this.accessType = accessType;
|
||||||
|
this.user = user;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (obj == null
|
@Override
|
||||||
|| !(obj instanceof AuthorizationComponent)) {
|
public int hashCode() {
|
||||||
return false;
|
return this.wasbAbsolutePath.hashCode() ^ this.accessType.hashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
return ((AuthorizationComponent) obj).
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
|
||||||
|
if (obj == this) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj == null
|
||||||
|
|| !(obj instanceof AuthorizationComponent)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ((AuthorizationComponent) obj).
|
||||||
getWasbAbsolutePath().equals(this.wasbAbsolutePath)
|
getWasbAbsolutePath().equals(this.wasbAbsolutePath)
|
||||||
&& ((AuthorizationComponent) obj).
|
&& ((AuthorizationComponent) obj).
|
||||||
getAccessType().equals(this.accessType);
|
getAccessType().equals(this.accessType)
|
||||||
}
|
&& ((AuthorizationComponent) obj).
|
||||||
|
getUser().equals(this.user);
|
||||||
|
}
|
||||||
|
|
||||||
public String getWasbAbsolutePath() {
|
public String getWasbAbsolutePath() {
|
||||||
return this.wasbAbsolutePath;
|
return this.wasbAbsolutePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getAccessType() {
|
public String getAccessType() {
|
||||||
return accessType;
|
return accessType;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public String getUser() {
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,12 +19,14 @@
|
||||||
package org.apache.hadoop.fs.azure;
|
package org.apache.hadoop.fs.azure;
|
||||||
|
|
||||||
import java.security.PrivilegedExceptionAction;
|
import java.security.PrivilegedExceptionAction;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
import org.apache.hadoop.conf.Configuration;
|
import org.apache.hadoop.conf.Configuration;
|
||||||
import org.apache.hadoop.fs.FSDataInputStream;
|
import org.apache.hadoop.fs.FSDataInputStream;
|
||||||
import org.apache.hadoop.fs.FSDataOutputStream;
|
import org.apache.hadoop.fs.FSDataOutputStream;
|
||||||
import org.apache.hadoop.fs.Path;
|
import org.apache.hadoop.fs.Path;
|
||||||
import org.apache.hadoop.fs.contract.ContractTestUtils;
|
import org.apache.hadoop.fs.contract.ContractTestUtils;
|
||||||
|
import org.apache.hadoop.fs.permission.FsPermission;
|
||||||
import org.apache.hadoop.security.UserGroupInformation;
|
import org.apache.hadoop.security.UserGroupInformation;
|
||||||
import org.apache.hadoop.util.StringUtils;
|
import org.apache.hadoop.util.StringUtils;
|
||||||
|
|
||||||
|
@ -47,6 +49,13 @@ public class TestNativeAzureFileSystemAuthorization
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
protected MockWasbAuthorizerImpl authorizer;
|
protected MockWasbAuthorizerImpl authorizer;
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
protected static final short STICKYBIT_PERMISSION_CONSTANT = 1700;
|
||||||
|
@VisibleForTesting
|
||||||
|
protected static final String READ = WasbAuthorizationOperations.READ.toString();
|
||||||
|
@VisibleForTesting
|
||||||
|
protected static final String WRITE = WasbAuthorizationOperations.WRITE.toString();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Configuration createConfiguration() {
|
public Configuration createConfiguration() {
|
||||||
Configuration conf = super.createConfiguration();
|
Configuration conf = super.createConfiguration();
|
||||||
|
@ -80,14 +89,16 @@ public class TestNativeAzureFileSystemAuthorization
|
||||||
/**
|
/**
|
||||||
* Setup up permissions to allow a recursive delete for cleanup purposes.
|
* Setup up permissions to allow a recursive delete for cleanup purposes.
|
||||||
*/
|
*/
|
||||||
protected void allowRecursiveDelete(NativeAzureFileSystem fs, String path) {
|
protected void allowRecursiveDelete(NativeAzureFileSystem fs, String path)
|
||||||
|
throws IOException {
|
||||||
|
|
||||||
int index = path.lastIndexOf('/');
|
int index = path.lastIndexOf('/');
|
||||||
String parent = (index == 0) ? "/" : path.substring(0, index);
|
String parent = (index == 0) ? "/" : path.substring(0, index);
|
||||||
|
|
||||||
authorizer.deleteAllAuthRules();
|
authorizer.deleteAllAuthRules();
|
||||||
authorizer.addAuthRule(parent, WasbAuthorizationOperations.WRITE.toString(), true);
|
authorizer.addAuthRule(parent, WRITE, getCurrentUserShortName(), true);
|
||||||
authorizer.addAuthRule((path.endsWith("*") ? path : path+"*"), WasbAuthorizationOperations.WRITE.toString(), true);
|
authorizer.addAuthRule((path.endsWith("*") ? path : path+"*"), WRITE,
|
||||||
|
getCurrentUserShortName(), true);
|
||||||
fs.updateWasbAuthorizer(authorizer);
|
fs.updateWasbAuthorizer(authorizer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,6 +111,13 @@ public class TestNativeAzureFileSystemAuthorization
|
||||||
operation, path.makeQualified(fs.getUri(), fs.getWorkingDirectory())));
|
operation, path.makeQualified(fs.getUri(), fs.getWorkingDirectory())));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get current user short name for user context
|
||||||
|
*/
|
||||||
|
protected String getCurrentUserShortName() throws IOException {
|
||||||
|
return UserGroupInformation.getCurrentUser().getShortUserName();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Positive test to verify Create access check.
|
* Positive test to verify Create access check.
|
||||||
* The file is created directly under an existing folder.
|
* The file is created directly under an existing folder.
|
||||||
|
@ -112,7 +130,7 @@ public class TestNativeAzureFileSystemAuthorization
|
||||||
Path parentDir = new Path("/");
|
Path parentDir = new Path("/");
|
||||||
Path testPath = new Path(parentDir, "test.dat");
|
Path testPath = new Path(parentDir, "test.dat");
|
||||||
|
|
||||||
authorizer.addAuthRule("/", WasbAuthorizationOperations.WRITE.toString(), true);
|
authorizer.addAuthRuleForOwner("/", WRITE, true);
|
||||||
fs.updateWasbAuthorizer(authorizer);
|
fs.updateWasbAuthorizer(authorizer);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -137,7 +155,7 @@ public class TestNativeAzureFileSystemAuthorization
|
||||||
Path parentDir = new Path("/testCreateAccessCheckPositive/1/2/3");
|
Path parentDir = new Path("/testCreateAccessCheckPositive/1/2/3");
|
||||||
Path testPath = new Path(parentDir, "test.dat");
|
Path testPath = new Path(parentDir, "test.dat");
|
||||||
|
|
||||||
authorizer.addAuthRule("/", WasbAuthorizationOperations.WRITE.toString(), true);
|
authorizer.addAuthRuleForOwner("/", WRITE, true);
|
||||||
fs.updateWasbAuthorizer(authorizer);
|
fs.updateWasbAuthorizer(authorizer);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -164,7 +182,7 @@ public class TestNativeAzureFileSystemAuthorization
|
||||||
|
|
||||||
setExpectedFailureMessage("create", testPath);
|
setExpectedFailureMessage("create", testPath);
|
||||||
|
|
||||||
authorizer.addAuthRule("/", WasbAuthorizationOperations.WRITE.toString(), true);
|
authorizer.addAuthRuleForOwner("/", WRITE, true);
|
||||||
fs.updateWasbAuthorizer(authorizer);
|
fs.updateWasbAuthorizer(authorizer);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -188,8 +206,8 @@ public class TestNativeAzureFileSystemAuthorization
|
||||||
Path parentDir = new Path("/");
|
Path parentDir = new Path("/");
|
||||||
Path testPath = new Path(parentDir, "test.dat");
|
Path testPath = new Path(parentDir, "test.dat");
|
||||||
|
|
||||||
authorizer.addAuthRule("/", WasbAuthorizationOperations.WRITE.toString(), true);
|
authorizer.addAuthRuleForOwner("/", WRITE, true);
|
||||||
authorizer.addAuthRule(testPath.toString(), WasbAuthorizationOperations.WRITE.toString(), true);
|
authorizer.addAuthRuleForOwner(testPath.toString(), WRITE, true);
|
||||||
fs.updateWasbAuthorizer(authorizer);
|
fs.updateWasbAuthorizer(authorizer);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -215,7 +233,7 @@ public class TestNativeAzureFileSystemAuthorization
|
||||||
|
|
||||||
setExpectedFailureMessage("create", testPath);
|
setExpectedFailureMessage("create", testPath);
|
||||||
|
|
||||||
authorizer.addAuthRule("/", WasbAuthorizationOperations.WRITE.toString(), false);
|
authorizer.addAuthRuleForOwner("/", WRITE, false);
|
||||||
fs.updateWasbAuthorizer(authorizer);
|
fs.updateWasbAuthorizer(authorizer);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -239,8 +257,8 @@ public class TestNativeAzureFileSystemAuthorization
|
||||||
Path intermediateFolders = new Path(parentDir, "1/2/3/");
|
Path intermediateFolders = new Path(parentDir, "1/2/3/");
|
||||||
Path testPath = new Path(intermediateFolders, "test.dat");
|
Path testPath = new Path(intermediateFolders, "test.dat");
|
||||||
|
|
||||||
authorizer.addAuthRule("/", WasbAuthorizationOperations.WRITE.toString(), true);
|
authorizer.addAuthRuleForOwner("/", WRITE, true);
|
||||||
authorizer.addAuthRule(testPath.toString(), WasbAuthorizationOperations.READ.toString(), true);
|
authorizer.addAuthRuleForOwner(testPath.toString(), READ, true);
|
||||||
fs.updateWasbAuthorizer(authorizer);
|
fs.updateWasbAuthorizer(authorizer);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -266,8 +284,8 @@ public class TestNativeAzureFileSystemAuthorization
|
||||||
|
|
||||||
setExpectedFailureMessage("liststatus", testPath);
|
setExpectedFailureMessage("liststatus", testPath);
|
||||||
|
|
||||||
authorizer.addAuthRule("/", WasbAuthorizationOperations.WRITE.toString(), true);
|
authorizer.addAuthRuleForOwner("/", WRITE, true);
|
||||||
authorizer.addAuthRule(testPath.toString(), WasbAuthorizationOperations.READ.toString(), false);
|
authorizer.addAuthRuleForOwner(testPath.toString(), READ, false);
|
||||||
fs.updateWasbAuthorizer(authorizer);
|
fs.updateWasbAuthorizer(authorizer);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -291,8 +309,10 @@ public class TestNativeAzureFileSystemAuthorization
|
||||||
Path srcPath = new Path(parentDir, "test1.dat");
|
Path srcPath = new Path(parentDir, "test1.dat");
|
||||||
Path dstPath = new Path(parentDir, "test2.dat");
|
Path dstPath = new Path(parentDir, "test2.dat");
|
||||||
|
|
||||||
authorizer.addAuthRule("/", WasbAuthorizationOperations.WRITE.toString(), true); /* to create parentDir */
|
/* to create parentDir */
|
||||||
authorizer.addAuthRule(parentDir.toString(), WasbAuthorizationOperations.WRITE.toString(), true); /* for rename */
|
authorizer.addAuthRuleForOwner("/", WRITE, true);
|
||||||
|
/* for rename */
|
||||||
|
authorizer.addAuthRuleForOwner(parentDir.toString(), WRITE, true);
|
||||||
fs.updateWasbAuthorizer(authorizer);
|
fs.updateWasbAuthorizer(authorizer);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -321,8 +341,9 @@ public class TestNativeAzureFileSystemAuthorization
|
||||||
|
|
||||||
setExpectedFailureMessage("rename", srcPath);
|
setExpectedFailureMessage("rename", srcPath);
|
||||||
|
|
||||||
authorizer.addAuthRule("/", WasbAuthorizationOperations.WRITE.toString(), true); /* to create parent dir */
|
/* to create parent dir */
|
||||||
authorizer.addAuthRule(parentDir.toString(), WasbAuthorizationOperations.WRITE.toString(), false);
|
authorizer.addAuthRuleForOwner("/", WRITE, true);
|
||||||
|
authorizer.addAuthRuleForOwner(parentDir.toString(), WRITE, false);
|
||||||
fs.updateWasbAuthorizer(authorizer);
|
fs.updateWasbAuthorizer(authorizer);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -352,9 +373,9 @@ public class TestNativeAzureFileSystemAuthorization
|
||||||
|
|
||||||
setExpectedFailureMessage("rename", dstPath);
|
setExpectedFailureMessage("rename", dstPath);
|
||||||
|
|
||||||
authorizer.addAuthRule("/", WasbAuthorizationOperations.WRITE.toString(), true); /* to create parent dir */
|
authorizer.addAuthRuleForOwner("/", WRITE, true); /* to create parent dir */
|
||||||
authorizer.addAuthRule(parentSrcDir.toString(), WasbAuthorizationOperations.WRITE.toString(), true);
|
authorizer.addAuthRuleForOwner(parentSrcDir.toString(), WRITE, true);
|
||||||
authorizer.addAuthRule(parentDstDir.toString(), WasbAuthorizationOperations.WRITE.toString(), false);
|
authorizer.addAuthRuleForOwner(parentDstDir.toString(), WRITE, false);
|
||||||
fs.updateWasbAuthorizer(authorizer);
|
fs.updateWasbAuthorizer(authorizer);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -381,9 +402,9 @@ public class TestNativeAzureFileSystemAuthorization
|
||||||
Path parentDstDir = new Path("/testRenameAccessCheckPositiveDst");
|
Path parentDstDir = new Path("/testRenameAccessCheckPositiveDst");
|
||||||
Path dstPath = new Path(parentDstDir, "test2.dat");
|
Path dstPath = new Path(parentDstDir, "test2.dat");
|
||||||
|
|
||||||
authorizer.addAuthRule("/", WasbAuthorizationOperations.WRITE.toString(), true); /* to create parent dirs */
|
authorizer.addAuthRuleForOwner("/", WRITE, true); /* to create parent dirs */
|
||||||
authorizer.addAuthRule(parentSrcDir.toString(), WasbAuthorizationOperations.WRITE.toString(), true);
|
authorizer.addAuthRuleForOwner(parentSrcDir.toString(), WRITE, true);
|
||||||
authorizer.addAuthRule(parentDstDir.toString(), WasbAuthorizationOperations.WRITE.toString(), true);
|
authorizer.addAuthRuleForOwner(parentDstDir.toString(), WRITE, true);
|
||||||
fs.updateWasbAuthorizer(authorizer);
|
fs.updateWasbAuthorizer(authorizer);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -412,8 +433,8 @@ public class TestNativeAzureFileSystemAuthorization
|
||||||
Path parentDir = new Path("/testReadAccessCheckPositive");
|
Path parentDir = new Path("/testReadAccessCheckPositive");
|
||||||
Path testPath = new Path(parentDir, "test.dat");
|
Path testPath = new Path(parentDir, "test.dat");
|
||||||
|
|
||||||
authorizer.addAuthRule("/", WasbAuthorizationOperations.WRITE.toString(), true);
|
authorizer.addAuthRuleForOwner("/", WRITE, true);
|
||||||
authorizer.addAuthRule(testPath.toString(), WasbAuthorizationOperations.READ.toString(), true);
|
authorizer.addAuthRuleForOwner(testPath.toString(), READ, true);
|
||||||
fs.updateWasbAuthorizer(authorizer);
|
fs.updateWasbAuthorizer(authorizer);
|
||||||
|
|
||||||
FSDataInputStream inputStream = null;
|
FSDataInputStream inputStream = null;
|
||||||
|
@ -453,8 +474,8 @@ public class TestNativeAzureFileSystemAuthorization
|
||||||
|
|
||||||
setExpectedFailureMessage("read", testPath);
|
setExpectedFailureMessage("read", testPath);
|
||||||
|
|
||||||
authorizer.addAuthRule("/", WasbAuthorizationOperations.WRITE.toString(), true);
|
authorizer.addAuthRuleForOwner("/", WRITE, true);
|
||||||
authorizer.addAuthRule(testPath.toString(), WasbAuthorizationOperations.READ.toString(), false);
|
authorizer.addAuthRuleForOwner(testPath.toString(), READ, false);
|
||||||
fs.updateWasbAuthorizer(authorizer);
|
fs.updateWasbAuthorizer(authorizer);
|
||||||
|
|
||||||
FSDataInputStream inputStream = null;
|
FSDataInputStream inputStream = null;
|
||||||
|
@ -490,7 +511,7 @@ public class TestNativeAzureFileSystemAuthorization
|
||||||
Path parentDir = new Path("/");
|
Path parentDir = new Path("/");
|
||||||
Path testPath = new Path(parentDir, "test.dat");
|
Path testPath = new Path(parentDir, "test.dat");
|
||||||
|
|
||||||
authorizer.addAuthRule("/", WasbAuthorizationOperations.WRITE.toString(), true);
|
authorizer.addAuthRuleForOwner("/", WRITE, true);
|
||||||
fs.updateWasbAuthorizer(authorizer);
|
fs.updateWasbAuthorizer(authorizer);
|
||||||
try {
|
try {
|
||||||
fs.create(testPath);
|
fs.create(testPath);
|
||||||
|
@ -514,7 +535,7 @@ public class TestNativeAzureFileSystemAuthorization
|
||||||
|
|
||||||
setExpectedFailureMessage("delete", testPath);
|
setExpectedFailureMessage("delete", testPath);
|
||||||
|
|
||||||
authorizer.addAuthRule("/", WasbAuthorizationOperations.WRITE.toString(), true);
|
authorizer.addAuthRuleForOwner("/", WRITE, true);
|
||||||
fs.updateWasbAuthorizer(authorizer);
|
fs.updateWasbAuthorizer(authorizer);
|
||||||
try {
|
try {
|
||||||
fs.create(testPath);
|
fs.create(testPath);
|
||||||
|
@ -523,7 +544,7 @@ public class TestNativeAzureFileSystemAuthorization
|
||||||
|
|
||||||
/* Remove permissions for delete to force failure */
|
/* Remove permissions for delete to force failure */
|
||||||
authorizer.deleteAllAuthRules();
|
authorizer.deleteAllAuthRules();
|
||||||
authorizer.addAuthRule("/", WasbAuthorizationOperations.WRITE.toString(), false);
|
authorizer.addAuthRuleForOwner("/", WRITE, false);
|
||||||
fs.updateWasbAuthorizer(authorizer);
|
fs.updateWasbAuthorizer(authorizer);
|
||||||
|
|
||||||
fs.delete(testPath, false);
|
fs.delete(testPath, false);
|
||||||
|
@ -531,7 +552,7 @@ public class TestNativeAzureFileSystemAuthorization
|
||||||
finally {
|
finally {
|
||||||
/* Restore permissions to force a successful delete */
|
/* Restore permissions to force a successful delete */
|
||||||
authorizer.deleteAllAuthRules();
|
authorizer.deleteAllAuthRules();
|
||||||
authorizer.addAuthRule("/", WasbAuthorizationOperations.WRITE.toString(), true);
|
authorizer.addAuthRuleForOwner("/", WRITE, true);
|
||||||
fs.updateWasbAuthorizer(authorizer);
|
fs.updateWasbAuthorizer(authorizer);
|
||||||
|
|
||||||
fs.delete(testPath, false);
|
fs.delete(testPath, false);
|
||||||
|
@ -550,9 +571,9 @@ public class TestNativeAzureFileSystemAuthorization
|
||||||
Path parentDir = new Path("/testDeleteIntermediateFolder");
|
Path parentDir = new Path("/testDeleteIntermediateFolder");
|
||||||
Path testPath = new Path(parentDir, "1/2/test.dat");
|
Path testPath = new Path(parentDir, "1/2/test.dat");
|
||||||
|
|
||||||
authorizer.addAuthRule("/", WasbAuthorizationOperations.WRITE.toString(), true); // for create and delete
|
authorizer.addAuthRuleForOwner("/", WRITE, true); // for create and delete
|
||||||
authorizer.addAuthRule("/testDeleteIntermediateFolder*",
|
authorizer.addAuthRuleForOwner("/testDeleteIntermediateFolder*",
|
||||||
WasbAuthorizationOperations.WRITE.toString(), true); // for recursive delete
|
WRITE, true); // for recursive delete
|
||||||
fs.updateWasbAuthorizer(authorizer);
|
fs.updateWasbAuthorizer(authorizer);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -567,6 +588,365 @@ public class TestNativeAzureFileSystemAuthorization
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test to verify access check failure leaves intermediate folders undeleted.
|
||||||
|
* @throws Throwable
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testDeleteAuthCheckFailureLeavesFilesUndeleted() throws Throwable {
|
||||||
|
|
||||||
|
Path parentDir = new Path("/testDeleteAuthCheckFailureLeavesFilesUndeleted");
|
||||||
|
Path testPath1 = new Path(parentDir, "child1/test.dat");
|
||||||
|
Path testPath2 = new Path(parentDir, "child2/test.dat");
|
||||||
|
|
||||||
|
authorizer.addAuthRuleForOwner("/", WRITE, true);
|
||||||
|
authorizer.addAuthRuleForOwner("/testDeleteAuthCheckFailureLeavesFilesUndeleted*",
|
||||||
|
WRITE, true);
|
||||||
|
fs.updateWasbAuthorizer(authorizer);
|
||||||
|
|
||||||
|
try {
|
||||||
|
fs.create(testPath1);
|
||||||
|
fs.create(testPath2);
|
||||||
|
ContractTestUtils.assertPathExists(fs, "testPath1 was not created", testPath1);
|
||||||
|
ContractTestUtils.assertPathExists(fs, "testPath2 was not created", testPath2);
|
||||||
|
|
||||||
|
// revoke write on one of the child folders
|
||||||
|
authorizer.deleteAllAuthRules();
|
||||||
|
authorizer.addAuthRuleForOwner("/", WRITE, true);
|
||||||
|
authorizer.addAuthRuleForOwner("/testDeleteAuthCheckFailureLeavesFilesUndeleted",
|
||||||
|
WRITE, true);
|
||||||
|
authorizer.addAuthRuleForOwner("/testDeleteAuthCheckFailureLeavesFilesUndeleted/child2",
|
||||||
|
WRITE, true);
|
||||||
|
authorizer.addAuthRuleForOwner("/testDeleteAuthCheckFailureLeavesFilesUndeleted/child1",
|
||||||
|
WRITE, false);
|
||||||
|
|
||||||
|
assertFalse(fs.delete(parentDir, true));
|
||||||
|
|
||||||
|
// Assert that only child2 contents are deleted
|
||||||
|
ContractTestUtils.assertPathExists(fs, "child1 is deleted!", testPath1);
|
||||||
|
ContractTestUtils.assertPathDoesNotExist(fs, "child2 exists after deletion!", testPath2);
|
||||||
|
ContractTestUtils.assertPathDoesNotExist(fs, "child2 exists after deletion!",
|
||||||
|
new Path("/testDeleteAuthCheckFailureLeavesFilesUndeleted/childPath2"));
|
||||||
|
ContractTestUtils.assertPathExists(fs, "parentDir is deleted!", parentDir);
|
||||||
|
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
allowRecursiveDelete(fs, parentDir.toString());
|
||||||
|
fs.delete(parentDir, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Positive test to verify file delete with sticky bit on parent.
|
||||||
|
* @throws Throwable
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testSingleFileDeleteWithStickyBitPositive() throws Throwable {
|
||||||
|
|
||||||
|
Path parentDir = new Path("/testSingleFileDeleteWithStickyBitPositive");
|
||||||
|
Path testPath = new Path(parentDir, "test.dat");
|
||||||
|
|
||||||
|
authorizer.addAuthRuleForOwner("/", WRITE, true);
|
||||||
|
authorizer.addAuthRuleForOwner("/testSingleFileDeleteWithStickyBitPositive",
|
||||||
|
WRITE, true);
|
||||||
|
fs.updateWasbAuthorizer(authorizer);
|
||||||
|
|
||||||
|
try {
|
||||||
|
fs.create(testPath);
|
||||||
|
ContractTestUtils.assertPathExists(fs, "testPath was not created", testPath);
|
||||||
|
|
||||||
|
// set stickybit on parent directory
|
||||||
|
fs.setPermission(parentDir, new FsPermission(STICKYBIT_PERMISSION_CONSTANT));
|
||||||
|
|
||||||
|
assertTrue(fs.delete(testPath, true));
|
||||||
|
ContractTestUtils.assertPathDoesNotExist(fs,
|
||||||
|
"testPath exists after deletion!", testPath);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
allowRecursiveDelete(fs, parentDir.toString());
|
||||||
|
fs.delete(parentDir, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Negative test to verify file delete fails when sticky bit is set on parent
|
||||||
|
* and non-owner user performs delete
|
||||||
|
* @throws Throwable
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testSingleFileDeleteWithStickyBitNegative() throws Throwable {
|
||||||
|
|
||||||
|
Path parentDir = new Path("/testSingleFileDeleteWithStickyBitNegative");
|
||||||
|
Path testPath = new Path(parentDir, "test.dat");
|
||||||
|
|
||||||
|
expectedEx.expect(WasbAuthorizationException.class);
|
||||||
|
expectedEx.expectMessage(String.format("%s has sticky bit set. File %s cannot be deleted.",
|
||||||
|
parentDir.toString(), testPath.toString()));
|
||||||
|
|
||||||
|
authorizer.addAuthRuleForOwner("/", WRITE, true);
|
||||||
|
authorizer.addAuthRuleForOwner("/testSingleFileDeleteWithStickyBitNegative",
|
||||||
|
WRITE, true);
|
||||||
|
fs.updateWasbAuthorizer(authorizer);
|
||||||
|
|
||||||
|
try {
|
||||||
|
fs.create(testPath);
|
||||||
|
ContractTestUtils.assertPathExists(fs, "testPath was not created", testPath);
|
||||||
|
// set stickybit on parent directory
|
||||||
|
fs.setPermission(parentDir, new FsPermission(STICKYBIT_PERMISSION_CONSTANT));
|
||||||
|
|
||||||
|
UserGroupInformation dummyUser = UserGroupInformation.createUserForTesting(
|
||||||
|
"dummyUser", new String[] {"dummygroup"});
|
||||||
|
|
||||||
|
dummyUser.doAs(new PrivilegedExceptionAction<Void>() {
|
||||||
|
@Override
|
||||||
|
public Void run() throws Exception {
|
||||||
|
authorizer.addAuthRule(parentDir.toString(), WRITE,
|
||||||
|
getCurrentUserShortName(), true);
|
||||||
|
fs.delete(testPath, true);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
ContractTestUtils.assertPathExists(fs, "testPath should not be deleted!", testPath);
|
||||||
|
|
||||||
|
allowRecursiveDelete(fs, parentDir.toString());
|
||||||
|
fs.delete(parentDir, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Positive test to verify file and folder delete succeeds with stickybit
|
||||||
|
* when the owner of the files deletes the file.
|
||||||
|
* @throws Throwable
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testRecursiveDeleteSucceedsWithStickybit() throws Throwable {
|
||||||
|
|
||||||
|
Path parentDir = new Path("/testRecursiveDeleteSucceedsWithStickybit");
|
||||||
|
Path testFilePath = new Path(parentDir, "child/test.dat");
|
||||||
|
Path testFolderPath = new Path(parentDir, "child/testDirectory");
|
||||||
|
|
||||||
|
authorizer.addAuthRuleForOwner("/", WRITE, true);
|
||||||
|
authorizer.addAuthRuleForOwner("/testRecursiveDeleteSucceedsWithStickybit*",
|
||||||
|
WRITE, true);
|
||||||
|
fs.updateWasbAuthorizer(authorizer);
|
||||||
|
|
||||||
|
try {
|
||||||
|
fs.create(testFilePath);
|
||||||
|
ContractTestUtils.assertPathExists(fs, "file was not created", testFilePath);
|
||||||
|
fs.mkdirs(testFolderPath);
|
||||||
|
ContractTestUtils.assertPathExists(fs, "folder was not created", testFolderPath);
|
||||||
|
// set stickybit on child directory
|
||||||
|
fs.setPermission(new Path(parentDir, "child"),
|
||||||
|
new FsPermission(STICKYBIT_PERMISSION_CONSTANT));
|
||||||
|
// perform delete as owner of the files
|
||||||
|
assertTrue(fs.delete(parentDir, true));
|
||||||
|
ContractTestUtils.assertPathDoesNotExist(fs, "parentDir exists after deletion!", parentDir);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
allowRecursiveDelete(fs, parentDir.toString());
|
||||||
|
fs.delete(parentDir, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test to verify delete fails for child files and folders when
|
||||||
|
* non-owner user performs delete and stickybit is set on parent
|
||||||
|
* @throws Throwable
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testRecursiveDeleteFailsWithStickybit() throws Throwable {
|
||||||
|
|
||||||
|
Path parentDir = new Path("/testRecursiveDeleteFailsWithStickybit");
|
||||||
|
Path testFilePath = new Path(parentDir, "child/test.dat");
|
||||||
|
Path testFolderPath = new Path(parentDir, "child/testDirectory");
|
||||||
|
|
||||||
|
authorizer.addAuthRuleForOwner("/", WRITE, true);
|
||||||
|
authorizer.addAuthRuleForOwner("/testRecursiveDeleteFailsWithStickybit*",
|
||||||
|
WRITE, true);
|
||||||
|
fs.updateWasbAuthorizer(authorizer);
|
||||||
|
|
||||||
|
try {
|
||||||
|
fs.create(testFilePath);
|
||||||
|
ContractTestUtils.assertPathExists(fs, "file was not created", testFilePath);
|
||||||
|
fs.mkdirs(testFolderPath);
|
||||||
|
ContractTestUtils.assertPathExists(fs, "folder was not created", testFolderPath);
|
||||||
|
|
||||||
|
// set stickybit on child directory
|
||||||
|
fs.setPermission(new Path(parentDir, "child"),
|
||||||
|
new FsPermission(STICKYBIT_PERMISSION_CONSTANT));
|
||||||
|
|
||||||
|
UserGroupInformation dummyUser = UserGroupInformation.createUserForTesting(
|
||||||
|
"dummyUser", new String[] {"dummygroup"});
|
||||||
|
|
||||||
|
dummyUser.doAs(new PrivilegedExceptionAction<Void>() {
|
||||||
|
@Override
|
||||||
|
public Void run() throws Exception {
|
||||||
|
// Add auth rules for dummyuser
|
||||||
|
authorizer.addAuthRule("/", WRITE,
|
||||||
|
getCurrentUserShortName(), true);
|
||||||
|
authorizer.addAuthRule("/testRecursiveDeleteFailsWithStickybit*",
|
||||||
|
WRITE, getCurrentUserShortName(), true);
|
||||||
|
|
||||||
|
assertFalse(fs.delete(parentDir, true));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ContractTestUtils.assertPathExists(fs, "parentDir is deleted!", parentDir);
|
||||||
|
ContractTestUtils.assertPathExists(fs, "file is deleted!", testFilePath);
|
||||||
|
ContractTestUtils.assertPathExists(fs, "folder is deleted!", testFolderPath);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
allowRecursiveDelete(fs, parentDir.toString());
|
||||||
|
fs.delete(parentDir, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test delete scenario where sticky bit check leaves files/folders not owned
|
||||||
|
* by a specific user intact and the files owned by him/her are deleted
|
||||||
|
* @throws Throwable
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testDeleteSucceedsForOnlyFilesOwnedByUserWithStickybitSet()
|
||||||
|
throws Throwable {
|
||||||
|
|
||||||
|
Path parentDir = new Path("/testDeleteSucceedsForOnlyFilesOwnedByUserWithStickybitSet");
|
||||||
|
Path testFilePath = new Path(parentDir, "test.dat");
|
||||||
|
Path testFolderPath = new Path(parentDir, "testDirectory");
|
||||||
|
|
||||||
|
authorizer.addAuthRuleForOwner("/", WRITE, true);
|
||||||
|
authorizer.addAuthRuleForOwner(
|
||||||
|
"/testDeleteSucceedsForOnlyFilesOwnedByUserWithStickybitSet*",
|
||||||
|
WRITE, true);
|
||||||
|
fs.updateWasbAuthorizer(authorizer);
|
||||||
|
|
||||||
|
try {
|
||||||
|
fs.create(testFilePath);
|
||||||
|
ContractTestUtils.assertPathExists(fs, "file was not created", testFilePath);
|
||||||
|
|
||||||
|
fs.setPermission(parentDir, new FsPermission(STICKYBIT_PERMISSION_CONSTANT));
|
||||||
|
|
||||||
|
UserGroupInformation dummyUser = UserGroupInformation.createUserForTesting(
|
||||||
|
"dummyuser", new String[] {"dummygroup"});
|
||||||
|
dummyUser.doAs(new PrivilegedExceptionAction<Void>() {
|
||||||
|
@Override
|
||||||
|
public Void run() throws Exception {
|
||||||
|
authorizer.addAuthRule("/", WRITE,
|
||||||
|
getCurrentUserShortName(), true);
|
||||||
|
authorizer.addAuthRule("/testDeleteSucceedsForOnlyFilesOwnedByUserWithStickybitSet*",
|
||||||
|
WRITE, getCurrentUserShortName(), true);
|
||||||
|
|
||||||
|
fs.create(testFolderPath); // the folder will have owner as dummyuser
|
||||||
|
ContractTestUtils.assertPathExists(fs, "folder was not created", testFolderPath);
|
||||||
|
assertFalse(fs.delete(parentDir, true));
|
||||||
|
|
||||||
|
ContractTestUtils.assertPathDoesNotExist(fs, "folder should have been deleted!",
|
||||||
|
testFolderPath);
|
||||||
|
ContractTestUtils.assertPathExists(fs, "parentDir is deleted!", parentDir);
|
||||||
|
ContractTestUtils.assertPathExists(fs, "file is deleted!", testFilePath);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
allowRecursiveDelete(fs, parentDir.toString());
|
||||||
|
fs.delete(parentDir, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test delete scenario where sticky bit is set and the owner of parent
|
||||||
|
* directory can delete child files/folders which he does not own.
|
||||||
|
* This is according to the sticky bit behaviour specified in hdfs permission
|
||||||
|
* guide which is as follows - The sticky bit can be set on directories,
|
||||||
|
* preventing anyone except the superuser, directory owner or file owner
|
||||||
|
* from deleting or moving the files within the directory
|
||||||
|
* @throws Throwable
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testDeleteSucceedsForParentDirectoryOwnerUserWithStickybit() throws Throwable {
|
||||||
|
|
||||||
|
Path parentDir = new Path("/testDeleteSucceedsForParentDirectoryOwnerUserWithStickybit");
|
||||||
|
Path testFilePath = new Path(parentDir, "test.dat");
|
||||||
|
|
||||||
|
authorizer.addAuthRuleForOwner("/", WRITE, true);
|
||||||
|
authorizer.addAuthRuleForOwner(
|
||||||
|
"/testDeleteSucceedsForParentDirectoryOwnerUserWithStickybit*",
|
||||||
|
WRITE, true);
|
||||||
|
fs.updateWasbAuthorizer(authorizer);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// create folder with owner as current user
|
||||||
|
fs.mkdirs(parentDir);
|
||||||
|
ContractTestUtils.assertPathExists(fs, "folder was not created", parentDir);
|
||||||
|
|
||||||
|
// create child with owner as dummyUser
|
||||||
|
UserGroupInformation dummyUser = UserGroupInformation.createUserForTesting(
|
||||||
|
"dummyUser", new String[] {"dummygroup"});
|
||||||
|
dummyUser.doAs(new PrivilegedExceptionAction<Void>() {
|
||||||
|
@Override
|
||||||
|
public Void run() throws Exception {
|
||||||
|
authorizer.addAuthRule("/testDeleteSucceedsForParentDirectoryOwnerUserWithStickybit",
|
||||||
|
WRITE, getCurrentUserShortName(), true);
|
||||||
|
fs.create(testFilePath);
|
||||||
|
ContractTestUtils.assertPathExists(fs, "file was not created", testFilePath);
|
||||||
|
|
||||||
|
fs.setPermission(parentDir,
|
||||||
|
new FsPermission(STICKYBIT_PERMISSION_CONSTANT));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// invoke delete as current user
|
||||||
|
assertTrue(fs.delete(parentDir, true));
|
||||||
|
ContractTestUtils.assertPathDoesNotExist(fs, "parentDir is not deleted!", parentDir);
|
||||||
|
ContractTestUtils.assertPathDoesNotExist(fs, "file is not deleted!", testFilePath);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
allowRecursiveDelete(fs, parentDir.toString());
|
||||||
|
fs.delete(parentDir, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test to verify delete of root succeeds with proper permissions and
|
||||||
|
* leaves root after delete.
|
||||||
|
* @throws Throwable
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testDeleteScenarioForRoot() throws Throwable {
|
||||||
|
Path rootPath = new Path("/");
|
||||||
|
Path parentDir = new Path("/testDeleteScenarioForRoot");
|
||||||
|
Path testPath1 = new Path(parentDir, "child1/test.dat");
|
||||||
|
Path testPath2 = new Path(parentDir, "child2/testFolder");
|
||||||
|
|
||||||
|
authorizer.addAuthRuleForOwner("/", WRITE, true);
|
||||||
|
authorizer.addAuthRuleForOwner("/testDeleteScenarioForRoot*",
|
||||||
|
WRITE, true);
|
||||||
|
fs.updateWasbAuthorizer(authorizer);
|
||||||
|
|
||||||
|
try {
|
||||||
|
fs.create(testPath1);
|
||||||
|
fs.create(testPath2);
|
||||||
|
ContractTestUtils.assertPathExists(fs, "testPath1 was not created", testPath1);
|
||||||
|
ContractTestUtils.assertPathExists(fs, "testPath2 was not created", testPath2);
|
||||||
|
|
||||||
|
assertFalse(fs.delete(rootPath, true));
|
||||||
|
|
||||||
|
ContractTestUtils.assertPathDoesNotExist(fs, "file exists after deletion!", testPath1);
|
||||||
|
ContractTestUtils.assertPathDoesNotExist(fs, "folder exists after deletion!", testPath2);
|
||||||
|
ContractTestUtils.assertPathDoesNotExist(fs, "parentDir exists after deletion!", parentDir);
|
||||||
|
ContractTestUtils.assertPathExists(fs, "Root should not have been deleted!", rootPath);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
allowRecursiveDelete(fs, parentDir.toString());
|
||||||
|
fs.delete(parentDir, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Positive test for getFileStatus. No permissions are required for getting filestatus.
|
* Positive test for getFileStatus. No permissions are required for getting filestatus.
|
||||||
* @throws Throwable
|
* @throws Throwable
|
||||||
|
@ -587,7 +967,7 @@ public class TestNativeAzureFileSystemAuthorization
|
||||||
|
|
||||||
Path testPath = new Path("/testMkdirsAccessCheckPositive/1/2/3");
|
Path testPath = new Path("/testMkdirsAccessCheckPositive/1/2/3");
|
||||||
|
|
||||||
authorizer.addAuthRule("/", WasbAuthorizationOperations.WRITE.toString(), true);
|
authorizer.addAuthRuleForOwner("/", WRITE, true);
|
||||||
fs.updateWasbAuthorizer(authorizer);
|
fs.updateWasbAuthorizer(authorizer);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -609,7 +989,7 @@ public class TestNativeAzureFileSystemAuthorization
|
||||||
|
|
||||||
Path testPath = new Path("/testMkdirsWithExistingHierarchyCheckPositive1");
|
Path testPath = new Path("/testMkdirsWithExistingHierarchyCheckPositive1");
|
||||||
|
|
||||||
authorizer.addAuthRule("/", WasbAuthorizationOperations.WRITE.toString(), true);
|
authorizer.addAuthRuleForOwner("/", WRITE, true);
|
||||||
fs.updateWasbAuthorizer(authorizer);
|
fs.updateWasbAuthorizer(authorizer);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -636,11 +1016,11 @@ public class TestNativeAzureFileSystemAuthorization
|
||||||
Path childPath2 = new Path(childPath1, "2");
|
Path childPath2 = new Path(childPath1, "2");
|
||||||
Path childPath3 = new Path(childPath2, "3");
|
Path childPath3 = new Path(childPath2, "3");
|
||||||
|
|
||||||
authorizer.addAuthRule("/",
|
authorizer.addAuthRuleForOwner("/",
|
||||||
WasbAuthorizationOperations.WRITE.toString(), true);
|
WRITE, true);
|
||||||
|
|
||||||
authorizer.addAuthRule(childPath1.toString(),
|
authorizer.addAuthRuleForOwner(childPath1.toString(),
|
||||||
WasbAuthorizationOperations.WRITE.toString(), true);
|
WRITE, true);
|
||||||
|
|
||||||
fs.updateWasbAuthorizer(authorizer);
|
fs.updateWasbAuthorizer(authorizer);
|
||||||
|
|
||||||
|
@ -675,7 +1055,7 @@ public class TestNativeAzureFileSystemAuthorization
|
||||||
|
|
||||||
setExpectedFailureMessage("mkdirs", testPath);
|
setExpectedFailureMessage("mkdirs", testPath);
|
||||||
|
|
||||||
authorizer.addAuthRule("/", WasbAuthorizationOperations.WRITE.toString(), false);
|
authorizer.addAuthRuleForOwner("/", WRITE, false);
|
||||||
fs.updateWasbAuthorizer(authorizer);
|
fs.updateWasbAuthorizer(authorizer);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -697,13 +1077,96 @@ public class TestNativeAzureFileSystemAuthorization
|
||||||
|
|
||||||
Path testPath = new Path("/");
|
Path testPath = new Path("/");
|
||||||
|
|
||||||
authorizer.addAuthRule(testPath.toString(), WasbAuthorizationOperations.READ.toString(), true);
|
authorizer.addAuthRuleForOwner(testPath.toString(), READ, true);
|
||||||
fs.updateWasbAuthorizer(authorizer);
|
fs.updateWasbAuthorizer(authorizer);
|
||||||
|
|
||||||
Path testPathWithTripleSlash = new Path("wasb:///" + testPath);
|
Path testPathWithTripleSlash = new Path("wasb:///" + testPath);
|
||||||
fs.listStatus(testPathWithTripleSlash);
|
fs.listStatus(testPathWithTripleSlash);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test case when owner matches current user
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testOwnerPermissionPositive() throws Throwable {
|
||||||
|
|
||||||
|
Path parentDir = new Path("/testOwnerPermissionPositive");
|
||||||
|
Path testPath = new Path(parentDir, "test.data");
|
||||||
|
|
||||||
|
authorizer.addAuthRuleForOwner("/", WRITE, true);
|
||||||
|
authorizer.addAuthRuleForOwner(testPath.toString(), READ, true);
|
||||||
|
authorizer.addAuthRuleForOwner(parentDir.toString(), WRITE, true);
|
||||||
|
// additional rule used for assertPathExists
|
||||||
|
authorizer.addAuthRuleForOwner(parentDir.toString(), READ, true);
|
||||||
|
fs.updateWasbAuthorizer(authorizer);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// creates parentDir with owner as current user
|
||||||
|
fs.mkdirs(parentDir);
|
||||||
|
ContractTestUtils.assertPathExists(fs, "parentDir does not exist", parentDir);
|
||||||
|
|
||||||
|
fs.create(testPath);
|
||||||
|
fs.getFileStatus(testPath);
|
||||||
|
ContractTestUtils.assertPathExists(fs, "testPath does not exist", testPath);
|
||||||
|
|
||||||
|
fs.delete(parentDir, true);
|
||||||
|
ContractTestUtils.assertPathDoesNotExist(fs, "testPath does not exist", testPath);
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
allowRecursiveDelete(fs, parentDir.toString());
|
||||||
|
fs.delete(parentDir, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Negative test case for owner does not match current user
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testOwnerPermissionNegative() throws Throwable {
|
||||||
|
|
||||||
|
Path parentDir = new Path("/testOwnerPermissionNegative");
|
||||||
|
Path childDir = new Path(parentDir, "childDir");
|
||||||
|
|
||||||
|
setExpectedFailureMessage("mkdirs", childDir);
|
||||||
|
|
||||||
|
authorizer.addAuthRuleForOwner("/", WRITE, true);
|
||||||
|
authorizer.addAuthRuleForOwner(parentDir.toString(), WRITE, true);
|
||||||
|
|
||||||
|
fs.updateWasbAuthorizer(authorizer);
|
||||||
|
|
||||||
|
try{
|
||||||
|
fs.mkdirs(parentDir);
|
||||||
|
UserGroupInformation ugiSuperUser = UserGroupInformation.createUserForTesting(
|
||||||
|
"testuser", new String[] {});
|
||||||
|
|
||||||
|
ugiSuperUser.doAs(new PrivilegedExceptionAction<Void>() {
|
||||||
|
@Override
|
||||||
|
public Void run() throws Exception {
|
||||||
|
fs.mkdirs(childDir);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
allowRecursiveDelete(fs, parentDir.toString());
|
||||||
|
fs.delete(parentDir, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test to verify that retrieving owner information does not
|
||||||
|
* throw when file/folder does not exist
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testRetrievingOwnerDoesNotFailWhenFileDoesNotExist()
|
||||||
|
throws Throwable {
|
||||||
|
|
||||||
|
Path testdirectory = new Path("/testDirectory123454565");
|
||||||
|
|
||||||
|
String owner = fs.getOwnerForPath(testdirectory);
|
||||||
|
assertEquals("", owner);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Negative test for setOwner when Authorization is enabled.
|
* Negative test for setOwner when Authorization is enabled.
|
||||||
*/
|
*/
|
||||||
|
@ -714,7 +1177,7 @@ public class TestNativeAzureFileSystemAuthorization
|
||||||
|
|
||||||
Path testPath = new Path("/testSetOwnerNegative");
|
Path testPath = new Path("/testSetOwnerNegative");
|
||||||
|
|
||||||
authorizer.addAuthRule("/", WasbAuthorizationOperations.WRITE.toString(), true);
|
authorizer.addAuthRuleForOwner("/", WRITE, true);
|
||||||
fs.updateWasbAuthorizer(authorizer);
|
fs.updateWasbAuthorizer(authorizer);
|
||||||
|
|
||||||
String owner = null;
|
String owner = null;
|
||||||
|
@ -748,7 +1211,7 @@ public class TestNativeAzureFileSystemAuthorization
|
||||||
|
|
||||||
Path testPath = new Path("/testSetOwnerPositive");
|
Path testPath = new Path("/testSetOwnerPositive");
|
||||||
|
|
||||||
authorizer.addAuthRule("/", WasbAuthorizationOperations.WRITE.toString(), true);
|
authorizer.addAuthRuleForOwner("/", WRITE, true);
|
||||||
fs.updateWasbAuthorizer(authorizer);
|
fs.updateWasbAuthorizer(authorizer);
|
||||||
|
|
||||||
String newOwner = "newowner";
|
String newOwner = "newowner";
|
||||||
|
@ -793,7 +1256,7 @@ public class TestNativeAzureFileSystemAuthorization
|
||||||
Path testPath = new Path("/testSetOwnerPositiveWildcard");
|
Path testPath = new Path("/testSetOwnerPositiveWildcard");
|
||||||
|
|
||||||
authorizer.init(conf);
|
authorizer.init(conf);
|
||||||
authorizer.addAuthRule("/", WasbAuthorizationOperations.WRITE.toString(), true);
|
authorizer.addAuthRuleForOwner("/", WRITE, true);
|
||||||
fs.updateWasbAuthorizer(authorizer);
|
fs.updateWasbAuthorizer(authorizer);
|
||||||
|
|
||||||
String newOwner = "newowner";
|
String newOwner = "newowner";
|
||||||
|
@ -839,7 +1302,7 @@ public class TestNativeAzureFileSystemAuthorization
|
||||||
Path testPath = new Path("/testSetOwnerFailsForIllegalSetup");
|
Path testPath = new Path("/testSetOwnerFailsForIllegalSetup");
|
||||||
|
|
||||||
authorizer.init(conf);
|
authorizer.init(conf);
|
||||||
authorizer.addAuthRule("/", WasbAuthorizationOperations.WRITE.toString(), true);
|
authorizer.addAuthRuleForOwner("/", WRITE, true);
|
||||||
fs.updateWasbAuthorizer(authorizer);
|
fs.updateWasbAuthorizer(authorizer);
|
||||||
|
|
||||||
String owner = null;
|
String owner = null;
|
||||||
|
|
Loading…
Reference in New Issue