/*
* The Alluxio Open Foundation licenses this work under the Apache License, version 2.0
* (the "License"). You may not use this work except in compliance with the License, which is
* available at www.apache.org/licenses/LICENSE-2.0
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied, as more fully set forth in the License.
*
* See the NOTICE file distributed with this work for information regarding copyright ownership.
*/
package alluxio.master.file;
import alluxio.AlluxioURI;
import alluxio.AuthenticatedUserRule;
import alluxio.ConfigurationRule;
import alluxio.Constants;
import alluxio.LoginUserRule;
import alluxio.PropertyKey;
import alluxio.exception.AccessControlException;
import alluxio.exception.ExceptionMessage;
import alluxio.exception.FileDoesNotExistException;
import alluxio.master.MasterRegistry;
import alluxio.master.block.BlockMaster;
import alluxio.master.block.BlockMasterFactory;
import alluxio.master.file.meta.Inode;
import alluxio.master.file.meta.InodeDirectory;
import alluxio.master.file.meta.InodeFile;
import alluxio.master.file.meta.InodeTree;
import alluxio.master.file.meta.LockedInodePath;
import alluxio.master.file.meta.MutableLockedInodePath;
import alluxio.master.file.options.CompleteFileOptions;
import alluxio.master.file.options.CreateDirectoryOptions;
import alluxio.master.file.options.CreateFileOptions;
import alluxio.master.file.options.DeleteOptions;
import alluxio.master.file.options.FreeOptions;
import alluxio.master.file.options.ListStatusOptions;
import alluxio.master.file.options.RenameOptions;
import alluxio.master.file.options.SetAttributeOptions;
import alluxio.master.journal.Journal;
import alluxio.master.journal.JournalFactory;
import alluxio.security.GroupMappingServiceTestUtils;
import alluxio.security.authorization.Mode;
import alluxio.security.group.GroupMappingService;
import alluxio.util.CommonUtils;
import alluxio.util.SecurityUtils;
import alluxio.util.io.PathUtils;
import alluxio.wire.FileInfo;
import alluxio.wire.TtlAction;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.tuple.ImmutableTriple;
import org.apache.commons.lang3.tuple.Triple;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.rules.TemporaryFolder;
import org.mockito.Mockito;
import java.io.Closeable;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
* Unit test for {@link FileSystemMaster} when permission check is enabled by configure
* alluxio.security.authorization.permission.enabled=true.
*/
public final class PermissionCheckTest {
private static final String TEST_SUPER_GROUP = "test-supergroup";
/*
* The user and group mappings for testing are:
* admin -> admin
* user1 -> group1
* user2 -> group2
* user3 -> group1
* user4 -> test-supergroup
*/
private static final TestUser TEST_USER_ADMIN = new TestUser("admin", "admin");
private static final TestUser TEST_USER_1 = new TestUser("user1", "group1");
private static final TestUser TEST_USER_2 = new TestUser("user2", "group2");
private static final TestUser TEST_USER_3 = new TestUser("user3", "group1");
private static final TestUser TEST_USER_SUPERGROUP = new TestUser("user4", TEST_SUPER_GROUP);
/*
* The file structure for testing is:
* / admin admin 755
* /testDir user1 group1 755
* /testDir/file user1 group1 644
* /testFile user2 group2 644
*/
private static final String TEST_DIR_URI = "/testDir";
private static final String TEST_DIR_FILE_URI = "/testDir/file";
private static final String TEST_FILE_URI = "/testFile";
private static final Mode TEST_DIR_MODE = new Mode((short) 0755);
private static final Mode TEST_FILE_MODE = new Mode((short) 0755);
private MasterRegistry mRegistry;
private FileSystemMaster mFileSystemMaster;
private BlockMaster mBlockMaster;
private InodeTree mInodeTree;
@Rule
public ConfigurationRule mConfiguration = new ConfigurationRule(ImmutableMap
.of(PropertyKey.SECURITY_GROUP_MAPPING_CLASS, FakeUserGroupsMapping.class.getName(),
PropertyKey.SECURITY_AUTHORIZATION_PERMISSION_SUPERGROUP, TEST_SUPER_GROUP));
@Rule
public AuthenticatedUserRule mAuthenticatedUser =
new AuthenticatedUserRule(TEST_USER_ADMIN.getUser());
@Rule
public LoginUserRule mLoginUserRule = new LoginUserRule(TEST_USER_ADMIN.getUser());
@Rule
public TemporaryFolder mTestFolder = new TemporaryFolder();
@Rule
public ExpectedException mThrown = ExpectedException.none();
/**
* A simple structure to represent a user and its groups.
*/
private static final class TestUser {
private String mUser;
private String mGroup;
TestUser(String user, String group) {
mUser = user;
mGroup = group;
}
String getUser() {
return mUser;
}
String getGroup() {
return mGroup;
}
}
/**
* A mapping from a user to its corresponding group.
*/
public static class FakeUserGroupsMapping implements GroupMappingService {
private HashMap<String, String> mUserGroups = new HashMap<>();
public FakeUserGroupsMapping() {
mUserGroups.put(TEST_USER_ADMIN.getUser(), TEST_USER_ADMIN.getGroup());
mUserGroups.put(TEST_USER_1.getUser(), TEST_USER_1.getGroup());
mUserGroups.put(TEST_USER_2.getUser(), TEST_USER_2.getGroup());
mUserGroups.put(TEST_USER_3.getUser(), TEST_USER_3.getGroup());
mUserGroups.put(TEST_USER_SUPERGROUP.getUser(), TEST_USER_SUPERGROUP.getGroup());
}
@Override
public List<String> getGroups(String user) throws IOException {
if (mUserGroups.containsKey(user)) {
return Lists.newArrayList(mUserGroups.get(user).split(","));
}
return new ArrayList<>();
}
}
@Before
public void before() throws Exception {
GroupMappingServiceTestUtils.resetCache();
mRegistry = new MasterRegistry();
JournalFactory factory =
new Journal.Factory(new URI(mTestFolder.newFolder().getAbsolutePath()));
mBlockMaster = new BlockMasterFactory().create(mRegistry, factory);
mFileSystemMaster = new FileSystemMasterFactory().create(mRegistry, factory);
mRegistry.start(true);
createDirAndFileForTest();
mInodeTree = Mockito.mock(InodeTree.class);
Mockito.when(mInodeTree.getRootUserName()).thenReturn(TEST_USER_ADMIN.getUser());
}
@After
public void after() throws Exception {
mRegistry.stop();
GroupMappingServiceTestUtils.resetCache();
}
/**
* Sets up the following file system structure for testing.
* / admin admin 755
* /testDir user1 group1 755
* /testDir/file user1 group1 644
* /testFile user2 group2 644
*/
private void createDirAndFileForTest() throws Exception {
// create "/testDir" for user1
try (Closeable r = new AuthenticatedUserRule(TEST_USER_ADMIN.getUser()).toResource()) {
mFileSystemMaster.createDirectory(new AlluxioURI("/testDir"),
CreateDirectoryOptions.defaults().setOwner(TEST_USER_1.getUser())
.setGroup(TEST_USER_1.getGroup()).setMode(TEST_DIR_MODE));
}
// create "/testDir/file" for user1
try (Closeable r = new AuthenticatedUserRule(TEST_USER_1.getUser()).toResource()) {
mFileSystemMaster.createFile(new AlluxioURI("/testDir/file"),
CreateFileOptions.defaults().setBlockSizeBytes(Constants.KB)
.setOwner(TEST_USER_1.getUser()).setGroup(TEST_USER_1.getGroup())
.setMode(TEST_FILE_MODE));
}
// create "/testFile" for user2
try (Closeable r = new AuthenticatedUserRule(TEST_USER_ADMIN.getUser()).toResource()) {
mFileSystemMaster.createFile(new AlluxioURI("/testFile"),
CreateFileOptions.defaults().setBlockSizeBytes(Constants.KB)
.setOwner(TEST_USER_2.getUser()).setGroup(TEST_USER_2.getGroup())
.setMode(TEST_FILE_MODE));
}
}
private InodeDirectory getRootInode() {
return InodeDirectory.create(0, -1, "",
CreateDirectoryOptions.defaults().setOwner(TEST_USER_ADMIN.getUser())
.setGroup(TEST_USER_ADMIN.getGroup()).setMode(TEST_DIR_MODE));
}
@Test
public void getPermissionOwner() throws Exception {
ArrayList<Triple<String, String, Mode>> permissions = new ArrayList<>();
permissions.add(new ImmutableTriple<>(TEST_USER_1.getUser(), TEST_USER_1.getGroup(),
new Mode((short) 0754)));
LockedInodePath lockedInodePath = getLockedInodePath(permissions);
try (Closeable r = new AuthenticatedUserRule(TEST_USER_1.getUser()).toResource()) {
PermissionChecker checker = new PermissionChecker(mInodeTree);
Mode.Bits actual = checker.getPermission(lockedInodePath);
Assert.assertEquals(Mode.Bits.ALL, actual);
}
}
@Test
public void getPermissionGroup() throws Exception {
ArrayList<Triple<String, String, Mode>> permissions = new ArrayList<>();
permissions.add(new ImmutableTriple<>(TEST_USER_1.getUser(), TEST_USER_1.getGroup(),
new Mode((short) 0754)));
LockedInodePath lockedInodePath = getLockedInodePath(permissions);
try (Closeable r = new AuthenticatedUserRule(TEST_USER_3.getUser()).toResource()) {
PermissionChecker checker = new PermissionChecker(mInodeTree);
Mode.Bits actual = checker.getPermission(lockedInodePath);
Assert.assertEquals(Mode.Bits.READ_EXECUTE, actual);
}
}
@Test
public void getPermissionOther() throws Exception {
ArrayList<Triple<String, String, Mode>> permissions = new ArrayList<>();
permissions.add(new ImmutableTriple<>(TEST_USER_1.getUser(), TEST_USER_1.getGroup(),
new Mode((short) 0754)));
LockedInodePath lockedInodePath = getLockedInodePath(permissions);
try (Closeable r = new AuthenticatedUserRule(TEST_USER_2.getUser()).toResource()) {
PermissionChecker checker = new PermissionChecker(mInodeTree);
Mode.Bits actual = checker.getPermission(lockedInodePath);
Assert.assertEquals(Mode.Bits.READ, actual);
}
}
/**
* Tests superuser and supergroup to create directories under root.
*/
@Test
public void createUnderRootAsAdmin() throws Exception {
// create "/file_admin" for superuser
verifyCreateFile(TEST_USER_ADMIN, "/file_admin", false);
// create "/file_supergroup" for user in supergroup
verifyCreateFile(TEST_USER_SUPERGROUP, "/file_supergroup", false);
}
/**
* Tests user1 to create directories under root.
*/
@Test
public void createUnderRootFail() throws Exception {
mThrown.expect(AccessControlException.class);
mThrown.expectMessage(ExceptionMessage.PERMISSION_DENIED
.getMessage(toExceptionMessage(TEST_USER_1.getUser(), Mode.Bits.WRITE, "/file1", "/")));
// create "/file1" for user1
verifyCreateFile(TEST_USER_1, "/file1", false);
}
@Test
public void createSuccess() throws Exception {
// create "/testDir/file1" for user1
verifyCreateFile(TEST_USER_1, TEST_DIR_URI + "/file1", false);
}
@Test
public void createFail() throws Exception {
mThrown.expect(AccessControlException.class);
mThrown.expectMessage(ExceptionMessage.PERMISSION_DENIED.getMessage(
toExceptionMessage(TEST_USER_2.getUser(), Mode.Bits.WRITE, TEST_DIR_URI + "/file1",
"testDir")));
// create "/testDir/file1" for user2
verifyCreateFile(TEST_USER_2, TEST_DIR_URI + "/file1", false);
}
private void verifyCreateFile(TestUser user, String path, boolean recursive) throws Exception {
try (Closeable r = new AuthenticatedUserRule(user.getUser()).toResource()) {
CreateFileOptions options = CreateFileOptions.defaults().setRecursive(recursive)
.setOwner(SecurityUtils.getOwnerFromThriftClient())
.setGroup(SecurityUtils.getGroupFromThriftClient()).setPersisted(true);
long fileId = mFileSystemMaster.createFile(new AlluxioURI(path), options);
FileInfo fileInfo = mFileSystemMaster.getFileInfo(fileId);
String[] pathComponents = path.split("/");
Assert.assertEquals(pathComponents[pathComponents.length - 1], fileInfo.getName());
Assert.assertEquals(user.getUser(), fileInfo.getOwner());
}
}
@Test
public void mkdirUnderRootByAdmin() throws Exception {
// createDirectory "/dir_admin" for superuser
verifyCreateDirectory(TEST_USER_ADMIN, "/dir_admin", false);
}
@Test
public void mkdirUnderRootBySupergroup() throws Exception {
// createDirectory "/dir_admin" for superuser
verifyCreateDirectory(TEST_USER_SUPERGROUP, "/dir_admin", false);
}
@Test
public void mkdirUnderRootByUser() throws Exception {
mThrown.expect(AccessControlException.class);
mThrown.expectMessage(ExceptionMessage.PERMISSION_DENIED
.getMessage(toExceptionMessage(TEST_USER_1.getUser(), Mode.Bits.WRITE, "/dir1", "/")));
// createDirectory "/dir1" for user1
verifyCreateDirectory(TEST_USER_1, "/dir1", false);
}
@Test
public void mkdirSuccess() throws Exception {
// createDirectory "/testDir/dir1" for user1
verifyCreateDirectory(TEST_USER_1, TEST_DIR_URI + "/dir1", false);
}
@Test
public void mkdirFail() throws Exception {
mThrown.expect(AccessControlException.class);
mThrown.expectMessage(ExceptionMessage.PERMISSION_DENIED.getMessage(
toExceptionMessage(TEST_USER_2.getUser(), Mode.Bits.WRITE, TEST_DIR_URI + "/dir1",
"testDir")));
// createDirectory "/testDir/dir1" for user2
verifyCreateDirectory(TEST_USER_2, TEST_DIR_URI + "/dir1", false);
}
private void verifyCreateDirectory(TestUser user, String path, boolean recursive)
throws Exception {
try (Closeable r = new AuthenticatedUserRule(user.getUser()).toResource()) {
CreateDirectoryOptions options = CreateDirectoryOptions.defaults().setRecursive(recursive)
.setOwner(SecurityUtils.getOwnerFromThriftClient())
.setGroup(SecurityUtils.getGroupFromThriftClient());
mFileSystemMaster.createDirectory(new AlluxioURI(path), options);
FileInfo fileInfo =
mFileSystemMaster.getFileInfo(mFileSystemMaster.getFileId(new AlluxioURI(path)));
String[] pathComponents = path.split("/");
Assert.assertEquals(pathComponents[pathComponents.length - 1], fileInfo.getName());
Assert.assertEquals(true, fileInfo.isFolder());
Assert.assertEquals(user.getUser(), fileInfo.getOwner());
}
}
@Test
public void renameUnderRootAsAdmin() throws Exception {
// rename "/testFile" to "/testFileRenamed" for superuser
verifyRename(TEST_USER_ADMIN, TEST_FILE_URI, "/testFileRenamed");
}
@Test
public void renameUnderRootAsSupergroup() throws Exception {
// rename "/testFile" to "/testFileRenamed" for user in supergroup
verifyRename(TEST_USER_SUPERGROUP, TEST_FILE_URI, "/testFileRenamed");
}
@Test
public void renameUnderRootFail() throws Exception {
mThrown.expect(AccessControlException.class);
mThrown.expectMessage(ExceptionMessage.PERMISSION_DENIED.getMessage(
toExceptionMessage(TEST_USER_1.getUser(), Mode.Bits.WRITE, TEST_FILE_URI, "/")));
// rename "/testFile" to "/testFileRenamed" for user1
verifyRename(TEST_USER_1, TEST_FILE_URI, "/testFileRenamed");
}
@Test
public void renameSuccess() throws Exception {
// rename "/testDir/file" to "/testDir/fileRenamed" for user1
verifyRename(TEST_USER_1, TEST_DIR_FILE_URI, "/testDir/fileRenamed");
}
@Test
public void renameFailNotByPermission() throws Exception {
mThrown.expect(FileDoesNotExistException.class);
mThrown.expectMessage(ExceptionMessage.PATH_DOES_NOT_EXIST.getMessage("/testDir/notExistDir"));
// rename "/testDir/file" to "/testDir/notExistDir/fileRenamed" for user1
// This is permitted by permission checking model, but failed during renaming procedure,
// since the impl cannot rename a file to a dst path whose parent does not exist.
verifyRename(TEST_USER_1, TEST_DIR_FILE_URI, "/testDir/notExistDir/fileRenamed");
}
@Test
public void renameFailBySrc() throws Exception {
mThrown.expect(AccessControlException.class);
mThrown.expectMessage(ExceptionMessage.PERMISSION_DENIED.getMessage(
toExceptionMessage(TEST_USER_2.getUser(), Mode.Bits.WRITE, TEST_DIR_FILE_URI, "testDir")));
// rename "/testDir/file" to "/file" for user2
verifyRename(TEST_USER_2, TEST_DIR_FILE_URI, "/file");
}
@Test
public void renameFailByDst() throws Exception {
mThrown.expect(AccessControlException.class);
mThrown.expectMessage(ExceptionMessage.PERMISSION_DENIED.getMessage(
toExceptionMessage(TEST_USER_1.getUser(), Mode.Bits.WRITE, "/fileRenamed", "/")));
// rename "/testDir/file" to "/fileRenamed" for user2
verifyRename(TEST_USER_1, TEST_DIR_FILE_URI, "/fileRenamed");
}
private void verifyRename(TestUser user, String srcPath, String dstPath) throws Exception {
try (Closeable r = new AuthenticatedUserRule(user.getUser()).toResource()) {
String fileOwner =
mFileSystemMaster.getFileInfo(mFileSystemMaster.getFileId(new AlluxioURI(srcPath)))
.getOwner();
mFileSystemMaster
.rename(new AlluxioURI(srcPath), new AlluxioURI(dstPath), RenameOptions.defaults());
Assert.assertEquals(-1, mFileSystemMaster.getFileId(new AlluxioURI(srcPath)));
FileInfo fileInfo =
mFileSystemMaster.getFileInfo(mFileSystemMaster.getFileId(new AlluxioURI(dstPath)));
String[] pathComponents = dstPath.split("/");
Assert.assertEquals(pathComponents[pathComponents.length - 1], fileInfo.getName());
Assert.assertEquals(fileOwner, fileInfo.getOwner());
}
}
@Test
public void deleteUnderRootFailed() throws Exception {
mThrown.expect(AccessControlException.class);
mThrown.expectMessage(ExceptionMessage.PERMISSION_DENIED
.getMessage(toExceptionMessage(TEST_USER_1.getUser(), Mode.Bits.WRITE, TEST_DIR_URI, "/")));
// delete file and dir under root by owner
verifyDelete(TEST_USER_1, TEST_DIR_URI, true);
}
@Test
public void deleteSuccessBySuperuser() throws Exception {
// delete file and dir by superuser
verifyDelete(TEST_USER_ADMIN, TEST_DIR_FILE_URI, false);
verifyDelete(TEST_USER_ADMIN, TEST_DIR_URI, true);
verifyDelete(TEST_USER_ADMIN, TEST_FILE_URI, false);
}
@Test
public void deleteSuccessBySupergroup() throws Exception {
// delete file and dir by user in supergroup
verifyDelete(TEST_USER_SUPERGROUP, TEST_DIR_FILE_URI, false);
verifyDelete(TEST_USER_SUPERGROUP, TEST_DIR_URI, true);
verifyDelete(TEST_USER_SUPERGROUP, TEST_FILE_URI, false);
}
@Test
public void deleteUnderRootFailOnDir() throws Exception {
mThrown.expect(AccessControlException.class);
mThrown.expectMessage(ExceptionMessage.PERMISSION_DENIED
.getMessage(toExceptionMessage(TEST_USER_2.getUser(), Mode.Bits.WRITE, TEST_DIR_URI, "/")));
// user2 cannot delete "/testDir" under root
verifyDelete(TEST_USER_2, TEST_DIR_URI, true);
}
@Test
public void deleteUnderRootFailOnFile() throws Exception {
mThrown.expect(AccessControlException.class);
mThrown.expectMessage(ExceptionMessage.PERMISSION_DENIED.getMessage(
toExceptionMessage(TEST_USER_1.getUser(), Mode.Bits.WRITE, TEST_FILE_URI, "/")));
// user2 cannot delete "/testFile" under root
verifyDelete(TEST_USER_1, TEST_FILE_URI, true);
}
@Test
public void deleteSuccess() throws Exception {
// user1 can delete its file
verifyDelete(TEST_USER_1, TEST_DIR_FILE_URI, false);
}
@Test
public void deleteFail() throws Exception {
mThrown.expect(AccessControlException.class);
mThrown.expectMessage(ExceptionMessage.PERMISSION_DENIED.getMessage(
toExceptionMessage(TEST_USER_2.getUser(), Mode.Bits.WRITE, TEST_DIR_FILE_URI, "testDir")));
// user 2 cannot delete "/testDir/file"
verifyDelete(TEST_USER_2, TEST_DIR_FILE_URI, false);
}
private void verifyDelete(TestUser user, String path, boolean recursive) throws Exception {
try (Closeable r = new AuthenticatedUserRule(user.getUser()).toResource()) {
mFileSystemMaster.delete(new AlluxioURI(path), DeleteOptions.defaults()
.setRecursive(recursive));
Assert.assertEquals(-1, mFileSystemMaster.getFileId(new AlluxioURI(path)));
}
}
@Test
public void readSuccess() throws Exception {
verifyRead(TEST_USER_1, TEST_DIR_FILE_URI, true);
verifyRead(TEST_USER_1, TEST_DIR_URI, false);
verifyRead(TEST_USER_1, TEST_FILE_URI, true);
verifyRead(TEST_USER_2, TEST_DIR_FILE_URI, true);
}
@Test
public void readFileIdFail() throws Exception {
String file = createUnreadableFileOrDir(true);
mThrown.expect(AccessControlException.class);
mThrown.expectMessage(ExceptionMessage.PERMISSION_DENIED.getMessage(
toExceptionMessage(TEST_USER_2.getUser(), Mode.Bits.READ, file, "onlyReadByUser1")));
verifyGetFileId(TEST_USER_2, file);
}
@Test
public void readFileInfoFail() throws Exception {
String file = createUnreadableFileOrDir(true);
mThrown.expect(AccessControlException.class);
mThrown.expectMessage(ExceptionMessage.PERMISSION_DENIED.getMessage(
toExceptionMessage(TEST_USER_2.getUser(), Mode.Bits.READ, file, "onlyReadByUser1")));
verifyGetFileInfoOrList(TEST_USER_2, file, true);
}
@Test
public void readDirIdFail() throws Exception {
String dir = createUnreadableFileOrDir(false);
mThrown.expect(AccessControlException.class);
mThrown.expectMessage(ExceptionMessage.PERMISSION_DENIED.getMessage(
toExceptionMessage(TEST_USER_2.getUser(), Mode.Bits.READ, dir, "onlyReadByUser1")));
verifyGetFileId(TEST_USER_2, dir);
}
@Test
public void readDirInfoFail() throws Exception {
String dir = createUnreadableFileOrDir(false);
mThrown.expect(AccessControlException.class);
mThrown.expectMessage(ExceptionMessage.PERMISSION_DENIED.getMessage(
toExceptionMessage(TEST_USER_2.getUser(), Mode.Bits.READ, dir, "onlyReadByUser1")));
try (Closeable r = new AuthenticatedUserRule(TEST_USER_2.getUser()).toResource()) {
verifyGetFileInfoOrList(TEST_USER_2, dir, false);
}
}
@Test
public void readNotExecuteDir() throws Exception {
// set unmask
try (Closeable c = new ConfigurationRule(
PropertyKey.SECURITY_AUTHORIZATION_PERMISSION_UMASK, "033").toResource()) {
String dir = PathUtils.concatPath(TEST_DIR_URI, "/notExecuteDir");
// create dir "/testDir/notExecuteDir" [user1, group1, drwxr--r--]
verifyCreateDirectory(TEST_USER_1, dir, false);
verifyRead(TEST_USER_1, dir, false);
mThrown.expect(AccessControlException.class);
mThrown.expectMessage(ExceptionMessage.PERMISSION_DENIED.getMessage(
toExceptionMessage(TEST_USER_2.getUser(), Mode.Bits.EXECUTE, dir, "notExecuteDir")));
verifyGetFileInfoOrList(TEST_USER_2, dir, false);
}
}
private String createUnreadableFileOrDir(boolean isFile) throws Exception {
// set unmask
try (Closeable c = new ConfigurationRule(
PropertyKey.SECURITY_AUTHORIZATION_PERMISSION_UMASK, "066").toResource()) {
String fileOrDir = PathUtils.concatPath(TEST_DIR_URI, "/onlyReadByUser1");
if (isFile) {
// create file "/testDir/onlyReadByUser1" [user1, group1, -rw-------]
verifyCreateFile(TEST_USER_1, fileOrDir, false);
verifyRead(TEST_USER_1, fileOrDir, true);
} else {
// create dir "/testDir/onlyReadByUser1" [user1, group1, drwx--x--x]
verifyCreateDirectory(TEST_USER_1, fileOrDir, false);
verifyRead(TEST_USER_1, fileOrDir, false);
}
return fileOrDir;
}
}
private void verifyRead(TestUser user, String path, boolean isFile) throws Exception {
try (Closeable r = new AuthenticatedUserRule(user.getUser()).toResource()) {
verifyGetFileId(user, path);
verifyGetFileInfoOrList(user, path, isFile);
}
}
private void verifyGetFileId(TestUser user, String path) throws Exception {
try (Closeable r = new AuthenticatedUserRule(user.getUser()).toResource()) {
long fileId = mFileSystemMaster.getFileId(new AlluxioURI(path));
Assert.assertNotEquals(-1, fileId);
}
}
private void verifyGetFileInfoOrList(TestUser user, String path, boolean isFile)
throws Exception {
try (Closeable r = new AuthenticatedUserRule(user.getUser()).toResource()) {
if (isFile) {
Assert.assertEquals(path, mFileSystemMaster.getFileInfo(new AlluxioURI(path)).getPath());
Assert.assertEquals(1,
mFileSystemMaster.listStatus(new AlluxioURI(path), ListStatusOptions.defaults())
.size());
} else {
List<FileInfo> fileInfoList =
mFileSystemMaster.listStatus(new AlluxioURI(path), ListStatusOptions.defaults());
if (fileInfoList.size() > 0) {
Assert.assertTrue(PathUtils.getParent(fileInfoList.get(0).getPath()).equals(path));
}
}
}
}
@Test
public void setStateSuccess() throws Exception {
// set unmask
try (Closeable c = new ConfigurationRule(
PropertyKey.SECURITY_AUTHORIZATION_PERMISSION_UMASK, "000").toResource()) {
String file = PathUtils.concatPath(TEST_DIR_URI, "testState1");
verifyCreateFile(TEST_USER_1, file, false);
SetAttributeOptions expect = getNonDefaultSetState();
SetAttributeOptions result = verifySetState(TEST_USER_2, file, expect);
Assert.assertEquals(expect.getTtl(), result.getTtl());
Assert.assertEquals(expect.getTtlAction(), result.getTtlAction());
Assert.assertEquals(expect.getPinned(), result.getPinned());
}
}
@Test
public void setStateFail() throws Exception {
// set unmask
try (Closeable c = new ConfigurationRule(
PropertyKey.SECURITY_AUTHORIZATION_PERMISSION_UMASK, "066").toResource()) {
String file = PathUtils.concatPath(TEST_DIR_URI, "testState1");
verifyCreateFile(TEST_USER_1, file, false);
SetAttributeOptions expect = getNonDefaultSetState();
mThrown.expect(AccessControlException.class);
mThrown.expectMessage(ExceptionMessage.PERMISSION_DENIED.getMessage(
toExceptionMessage(TEST_USER_2.getUser(), Mode.Bits.WRITE, file, "testState1")));
verifySetState(TEST_USER_2, file, expect);
}
}
private SetAttributeOptions getNonDefaultSetState() {
boolean recursive = true;
long ttl = 11;
return SetAttributeOptions.defaults().setPinned(recursive).setTtl(ttl)
.setTtlAction(TtlAction.DELETE);
}
private SetAttributeOptions verifySetState(TestUser user, String path,
SetAttributeOptions options) throws Exception {
try (Closeable r = new AuthenticatedUserRule(user.getUser()).toResource()) {
mFileSystemMaster.setAttribute(new AlluxioURI(path), options);
FileInfo fileInfo = mFileSystemMaster.getFileInfo(new AlluxioURI(path));
return SetAttributeOptions.defaults().setPinned(fileInfo.isPinned()).setTtl(fileInfo.getTtl())
.setPersisted(fileInfo.isPersisted());
}
}
@Test
public void completeFileSuccess() throws Exception {
// set unmask
try (Closeable c = new ConfigurationRule(
PropertyKey.SECURITY_AUTHORIZATION_PERMISSION_UMASK, "044").toResource()) {
String file = PathUtils.concatPath(TEST_DIR_URI, "/testState1");
verifyCreateFile(TEST_USER_1, file, false);
CompleteFileOptions expect = getNonDefaultCompleteFileOptions();
verifyCompleteFile(TEST_USER_2, file, expect);
}
}
@Test
public void completeFileFail() throws Exception {
// set unmask
try (Closeable c = new ConfigurationRule(
PropertyKey.SECURITY_AUTHORIZATION_PERMISSION_UMASK, "066").toResource()) {
String file = PathUtils.concatPath(TEST_DIR_URI, "/testComplete1");
verifyCreateFile(TEST_USER_1, file, false);
CompleteFileOptions expect = getNonDefaultCompleteFileOptions();
mThrown.expect(AccessControlException.class);
mThrown.expectMessage(ExceptionMessage.PERMISSION_DENIED.getMessage(
toExceptionMessage(TEST_USER_2.getUser(), Mode.Bits.WRITE, file, "testComplete1")));
verifyCompleteFile(TEST_USER_2, file, expect);
}
}
private CompleteFileOptions getNonDefaultCompleteFileOptions() {
long ufsLength = 12;
long operationTimeMs = 21;
return CompleteFileOptions.defaults().setUfsLength(ufsLength)
.setOperationTimeMs(operationTimeMs);
}
private void verifyCompleteFile(TestUser user, String path, CompleteFileOptions options)
throws Exception {
try (Closeable r = new AuthenticatedUserRule(user.getUser()).toResource()) {
mFileSystemMaster.completeFile(new AlluxioURI(path), options);
}
}
@Test
public void freeFileSuccess() throws Exception {
String file = PathUtils.concatPath(TEST_DIR_URI, "testState1");
verifyCreateFile(TEST_USER_1, file, false);
verifyFree(TEST_USER_2, file, false);
}
@Test
public void freeNonNullDirectorySuccess() throws Exception {
String subDir = PathUtils.concatPath(TEST_DIR_URI, "testState");
verifyCreateDirectory(TEST_USER_1, subDir, false);
String file = subDir + "/testState1";
verifyCreateFile(TEST_USER_1, file, false);
verifyFree(TEST_USER_2, subDir, true);
}
@Test
public void freeFileFail() throws Exception {
// set unmask
try (Closeable c = new ConfigurationRule(
PropertyKey.SECURITY_AUTHORIZATION_PERMISSION_UMASK, "066").toResource()) {
String file = PathUtils.concatPath(TEST_DIR_URI, "testComplete1");
verifyCreateFile(TEST_USER_1, file, false);
mThrown.expect(AccessControlException.class);
mThrown.expectMessage(ExceptionMessage.PERMISSION_DENIED.getMessage(
toExceptionMessage(TEST_USER_2.getUser(), Mode.Bits.READ, file, "testComplete1")));
verifyFree(TEST_USER_2, file, false);
}
}
@Test
public void freeNonNullDirectoryFail() throws Exception {
// set unmask
try (Closeable c = new ConfigurationRule(
PropertyKey.SECURITY_AUTHORIZATION_PERMISSION_UMASK, "066").toResource()) {
String file = PathUtils.concatPath(TEST_DIR_URI + "/testComplete1");
verifyCreateFile(TEST_USER_1, file, false);
mThrown.expect(AccessControlException.class);
mThrown.expectMessage(ExceptionMessage.PERMISSION_DENIED.getMessage(
toExceptionMessage(TEST_USER_2.getUser(), Mode.Bits.READ, file, "testComplete1")));
verifyFree(TEST_USER_2, file, false);
}
}
private void verifyFree(TestUser user, String path, boolean recursive) throws Exception {
try (Closeable r = new AuthenticatedUserRule(user.getUser()).toResource()) {
mFileSystemMaster.free(new AlluxioURI(path), FreeOptions.defaults().setRecursive(recursive));
}
}
@Test
public void setOwnerSuccess() throws Exception {
verifySetAcl(TEST_USER_ADMIN, TEST_FILE_URI, TEST_USER_1.getUser(), null, (short) -1, false);
verifySetAcl(TEST_USER_SUPERGROUP, TEST_DIR_URI, TEST_USER_2.getUser(), null, (short) -1, true);
FileInfo fileInfo = mFileSystemMaster
.getFileInfo(mFileSystemMaster.getFileId(new AlluxioURI(TEST_DIR_FILE_URI)));
Assert.assertEquals(TEST_USER_2.getUser(), fileInfo.getOwner());
}
@Test
public void setOwnerFail() throws Exception {
mThrown.expect(AccessControlException.class);
mThrown.expectMessage(TEST_USER_2.getUser() + " is not a super user or in super group");
verifySetAcl(TEST_USER_2, TEST_FILE_URI, TEST_USER_1.getUser(), null, (short) -1, false);
}
@Test
public void setGroupSuccess() throws Exception {
// super user
verifySetAcl(TEST_USER_ADMIN, TEST_FILE_URI, null, TEST_USER_1.getGroup(), (short) -1, false);
// super group
verifySetAcl(TEST_USER_SUPERGROUP, TEST_DIR_URI, null, TEST_USER_2.getGroup(), (short) -1,
true);
FileInfo fileInfo = mFileSystemMaster
.getFileInfo(mFileSystemMaster.getFileId(new AlluxioURI(TEST_DIR_FILE_URI)));
Assert.assertEquals(TEST_USER_2.getGroup(), fileInfo.getGroup());
// owner
verifySetAcl(TEST_USER_1, TEST_DIR_URI, null, TEST_USER_2.getGroup(), (short) -1, true);
fileInfo = mFileSystemMaster
.getFileInfo(mFileSystemMaster.getFileId(new AlluxioURI(TEST_DIR_FILE_URI)));
Assert.assertEquals(TEST_USER_2.getGroup(), fileInfo.getGroup());
}
@Test
public void setGroupFail() throws Exception {
mThrown.expect(AccessControlException.class);
mThrown.expectMessage(ExceptionMessage.PERMISSION_DENIED.getMessage(
"user=" + TEST_USER_1.getUser() + " is not the owner of path=" + TEST_FILE_URI));
verifySetAcl(TEST_USER_1, TEST_FILE_URI, null, TEST_USER_1.getGroup(), (short) -1, false);
}
@Test
public void setPermissionSuccess() throws Exception {
// super user
verifySetAcl(TEST_USER_ADMIN, TEST_FILE_URI, null, null, (short) 0600, false);
// super group
verifySetAcl(TEST_USER_SUPERGROUP, TEST_DIR_URI, null, null, (short) 0700, true);
FileInfo fileInfo = mFileSystemMaster
.getFileInfo(mFileSystemMaster.getFileId(new AlluxioURI(TEST_DIR_FILE_URI)));
Assert.assertEquals((short) 0700, fileInfo.getMode());
// owner enlarge the permission
verifySetAcl(TEST_USER_1, TEST_DIR_URI, null, null, (short) 0777, true);
fileInfo = mFileSystemMaster
.getFileInfo(mFileSystemMaster.getFileId(new AlluxioURI(TEST_DIR_FILE_URI)));
Assert.assertEquals((short) 0777, fileInfo.getMode());
// other user can operate under this enlarged permission
verifyCreateFile(TEST_USER_2, TEST_DIR_URI + "/newFile", false);
verifyDelete(TEST_USER_2, TEST_DIR_FILE_URI, false);
}
@Test
public void setPermissionFail() throws Exception {
mThrown.expect(AccessControlException.class);
mThrown.expectMessage(ExceptionMessage.PERMISSION_DENIED.getMessage(
"user=" + TEST_USER_1.getUser() + " is not the owner of path=" + TEST_FILE_URI));
verifySetAcl(TEST_USER_1, TEST_FILE_URI, null, null, (short) 0777, false);
}
@Test
public void setAclSuccess() throws Exception {
// super user sets owner, group, and permission
verifySetAcl(TEST_USER_ADMIN, TEST_FILE_URI, TEST_USER_1.getUser(), TEST_USER_1.getGroup(),
(short) 0600, false);
// owner sets group and permission
verifySetAcl(TEST_USER_1, TEST_DIR_URI, null, TEST_USER_2.getGroup(), (short) 0777, true);
FileInfo fileInfo = mFileSystemMaster
.getFileInfo(mFileSystemMaster.getFileId(new AlluxioURI(TEST_DIR_FILE_URI)));
Assert.assertEquals(TEST_USER_2.getGroup(), fileInfo.getGroup());
Assert.assertEquals((short) 0777, fileInfo.getMode());
}
@Test
public void setAclFailByNotSuperUser() throws Exception {
mThrown.expect(AccessControlException.class);
mThrown.expectMessage(TEST_USER_2.getUser() + " is not a super user or in super group");
verifySetAcl(TEST_USER_2, TEST_FILE_URI, TEST_USER_1.getUser(), TEST_USER_1.getGroup(),
(short) 0600, false);
}
private void verifySetAcl(TestUser runUser, String path, String owner, String group,
short mode, boolean recursive) throws Exception {
try (Closeable r = new AuthenticatedUserRule(runUser.getUser()).toResource()) {
SetAttributeOptions options =
SetAttributeOptions.defaults().setOwner(owner).setGroup(group).setMode(mode)
.setRecursive(recursive);
mFileSystemMaster.setAttribute(new AlluxioURI(path), options);
}
try (Closeable r = new AuthenticatedUserRule(TEST_USER_ADMIN.getUser()).toResource()) {
FileInfo fileInfo =
mFileSystemMaster.getFileInfo(mFileSystemMaster.getFileId(new AlluxioURI(path)));
if (owner != null) {
Assert.assertEquals(owner, fileInfo.getOwner());
}
if (group != null) {
Assert.assertEquals(group, fileInfo.getGroup());
}
if (mode != -1) {
Assert.assertEquals(mode, fileInfo.getMode());
}
}
}
private String toExceptionMessage(String user, Mode.Bits bits, String path, String inodeName) {
StringBuilder stringBuilder =
new StringBuilder().append("user=").append(user).append(", ").append("access=").append(bits)
.append(", ").append("path=").append(path).append(": ").append("failed at ")
.append(inodeName);
return stringBuilder.toString();
}
private LockedInodePath getLockedInodePath(ArrayList<Triple<String, String, Mode>> permissions)
throws Exception {
List<Inode<?>> inodes = new ArrayList<>();
inodes.add(getRootInode());
if (permissions.size() == 0) {
return new MutableLockedInodePath(new AlluxioURI("/"), inodes, null, InodeTree.LockMode.READ);
}
String uri = "";
for (int i = 0; i < permissions.size(); i++) {
Triple<String, String, Mode> permission = permissions.get(i);
String owner = permission.getLeft();
String group = permission.getMiddle();
Mode mode = permission.getRight();
uri += "/" + (i + 1);
if (i == permissions.size() - 1) {
Inode<?> inode = InodeFile.create(i + 1, i, (i + 1) + "", CommonUtils.getCurrentMs(),
CreateFileOptions.defaults().setBlockSizeBytes(Constants.KB).setOwner(owner)
.setGroup(group).setMode(mode));
inodes.add(inode);
} else {
Inode<?> inode = InodeDirectory.create(i + 1, i, (i + 1) + "",
CreateDirectoryOptions.defaults().setOwner(owner).setGroup(group).setMode(mode));
inodes.add(inode);
}
}
return new MutableLockedInodePath(new AlluxioURI(uri), inodes, null, InodeTree.LockMode.READ);
}
}