Java tutorial
// 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.impala.util; import java.io.IOException; import java.util.Arrays; import java.util.HashSet; import java.util.Set; import java.util.Map; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.permission.AclEntry; import org.apache.hadoop.fs.permission.AclEntryType; import org.apache.hadoop.fs.permission.AclStatus; import org.apache.hadoop.fs.permission.AclEntryScope; import org.apache.hadoop.fs.FileStatus; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.apache.hadoop.fs.permission.FsAction; import org.apache.hadoop.fs.permission.FsPermission; import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.hdfs.protocol.AclException; import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_PERMISSIONS_SUPERUSERGROUP_DEFAULT; import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_PERMISSIONS_SUPERUSERGROUP_KEY; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.Maps; import com.google.common.collect.Lists; /** * Singleton class that can check whether the current user has permission to access paths * in a FileSystem. */ public class FsPermissionChecker { private final static Logger LOG = LoggerFactory.getLogger(FsPermissionChecker.class); private final static FsPermissionChecker instance_; private final static Configuration CONF; protected final String user_; private final Set<String> groups_ = new HashSet<String>(); private final String supergroup_; static { CONF = new Configuration(); try { instance_ = new FsPermissionChecker(); } catch (IOException e) { throw new RuntimeException("Error initializing FsPermissionChecker: " + e.getMessage(), e); } } private FsPermissionChecker() throws IOException { UserGroupInformation ugi = UserGroupInformation.getCurrentUser(); groups_.addAll(Arrays.asList(ugi.getGroupNames())); supergroup_ = CONF.get(DFS_PERMISSIONS_SUPERUSERGROUP_KEY, DFS_PERMISSIONS_SUPERUSERGROUP_DEFAULT); user_ = ugi.getShortUserName(); } private boolean isSuperUser() { return groups_.contains(supergroup_); } private static List<AclEntryType> ACL_TYPE_PRIORITY = ImmutableList.of(AclEntryType.USER, AclEntryType.GROUP, AclEntryType.OTHER); /** * Allows checking different access permissions of a file without repeatedly accessing * the underlying filesystem by caching the results of a status call at construction. */ public class Permissions { private final FileStatus fileStatus_; private final FsPermission permissions_; private final AclStatus aclStatus_; private Map<AclEntryType, List<AclEntry>> entriesByTypes_ = Maps.newHashMap(); private AclEntry mask_; /** * If aclStatus is null, ACL permissions are not checked. */ protected Permissions(FileStatus fileStatus, AclStatus aclStatus) { Preconditions.checkNotNull(fileStatus); fileStatus_ = fileStatus; permissions_ = fileStatus.getPermission(); aclStatus_ = aclStatus; if (aclStatus_ == null) return; // Group the ACLs by type, so that we can apply them in correct priority order. Not // clear from documentation whether aclStatus_.getEntries() guarantees this // ordering, so this is defensive. for (AclEntryType t : ACL_TYPE_PRIORITY) { entriesByTypes_.put(t, Lists.<AclEntry>newArrayList()); } List<AclEntry> fullAclList = getAclFromPermAndEntries(permissions_, aclStatus_.getEntries()); for (AclEntry e : fullAclList) { if (e.getType() == AclEntryType.MASK && e.getScope() != AclEntryScope.DEFAULT) { mask_ = e; } else if (isApplicableAcl(e)) { entriesByTypes_.get(e.getType()).add(e); } } } /** * Returns true if the mask should apply. The mask ACL applies only to unnamed user * ACLs (e.g. user::r-x), and all group ACLs. */ private boolean shouldApplyMask(AclEntry acl) { if (mask_ == null) return false; switch (acl.getType()) { case USER: return acl.getName() != null; case GROUP: return true; } return false; } /** * Returns true if this ACL applies to the current user and / or group */ private boolean isApplicableAcl(AclEntry e) { // Default ACLs are not used for permission checking, but instead control the // permissions received by child directories if (e.getScope() == AclEntryScope.DEFAULT) return false; switch (e.getType()) { case USER: String aclUser = e.getName() == null ? aclStatus_.getOwner() : e.getName(); return FsPermissionChecker.this.user_.equals(aclUser); case GROUP: String aclGroup = e.getName() == null ? aclStatus_.getGroup() : e.getName(); return FsPermissionChecker.this.groups_.contains(aclGroup); case OTHER: return true; case MASK: return false; default: LOG.warn("Unknown Acl type: " + e.getType()); return false; } } /** * Returns true if ACLs allow 'action', false if they explicitly disallow 'action', * and 'null' if no ACLs are available. * See http://users.suse.com/~agruen/acl/linux-acls/online for more details about * acl access check algorithm. */ private Boolean checkAcls(FsAction action) { // ACLs may not be enabled, so we need this ternary logic. If no ACLs are available, // returning null causes us to fall back to standard ugo permissions. if (aclStatus_ == null) return null; // Remember if there is an applicable ACL entry, including owner user, named user, // owning group, named group. boolean foundMatch = false; for (AclEntryType t : ACL_TYPE_PRIORITY) { for (AclEntry e : entriesByTypes_.get(t)) { if (t == AclEntryType.OTHER) { // Processed all ACL entries except the OTHER entry. // If found applicable ACL entries but none of them contain requested // permission, deny access. Otherwise check OTHER entry. return foundMatch ? false : e.getPermission().implies(action); } // If there is an applicable mask, 'action' is allowed iff both the mask and // the underlying ACL permit it. if (e.getPermission().implies(action)) { if (shouldApplyMask(e)) { if (mask_.getPermission().implies(action)) return true; } else { return true; } } // User ACL entry has priority, no need to continue check. if (t == AclEntryType.USER) return false; foundMatch = true; } } return false; } /** * Returns true if the current user can perform the given action given these * permissions. */ public boolean checkPermissions(FsAction action) { if (FsPermissionChecker.this.isSuperUser()) return true; Boolean aclPerms = checkAcls(action); if (aclPerms != null) return aclPerms; // Check user, group and then 'other' permissions in turn. if (FsPermissionChecker.this.user_.equals(fileStatus_.getOwner())) { // If the user matches, we must return their access rights whether or not the user // is allowed to access without checking the group. This is counter-intuitive if // the user cannot access the file, but the group permissions would allow it, but // is consistent with UNIX behaviour. return permissions_.getUserAction().implies(action); } if (FsPermissionChecker.this.groups_.contains(fileStatus_.getGroup())) { return permissions_.getGroupAction().implies(action); } return permissions_.getOtherAction().implies(action); } public boolean canRead() { return checkPermissions(FsAction.READ); } public boolean canWrite() { return checkPermissions(FsAction.WRITE); } public boolean canReadAndWrite() { return canRead() && canWrite(); } // This was originally lifted from Hadoop. Won't need it if HDFS-7177 is resolved. // getAclStatus() returns just extended ACL entries, the default file permissions // like "user::,group::,other::" are not included. We need to combine them together // to get full logic ACL list. private List<AclEntry> getAclFromPermAndEntries(FsPermission perm, List<AclEntry> entries) { // File permission always have 3 items. List<AclEntry> aclEntries = Lists.newArrayListWithCapacity(entries.size() + 3); // Owner entry implied by owner permission bits. aclEntries.add(new AclEntry.Builder().setScope(AclEntryScope.ACCESS).setType(AclEntryType.USER) .setPermission(perm.getUserAction()).build()); // All extended access ACL entries add by "-setfacl" other than default file // permission. boolean hasAccessAcl = false; for (AclEntry entry : entries) { // AclEntry list should be ordered, all ACCESS one are in first half, DEFAULT one // are in second half, so no need to continue here. if (entry.getScope() == AclEntryScope.DEFAULT) break; hasAccessAcl = true; aclEntries.add(entry); } // Mask entry implied by group permission bits, or group entry if there is // no access ACL (only default ACL). aclEntries.add(new AclEntry.Builder().setScope(AclEntryScope.ACCESS) .setType(hasAccessAcl ? AclEntryType.MASK : AclEntryType.GROUP) .setPermission(perm.getGroupAction()).build()); // Other entry implied by other bits. aclEntries.add(new AclEntry.Builder().setScope(AclEntryScope.ACCESS).setType(AclEntryType.OTHER) .setPermission(perm.getOtherAction()).build()); return aclEntries; } } /** * Returns a Permissions object that can answer all access permission queries for the * given path. */ public Permissions getPermissions(FileSystem fs, Path path) throws IOException { Preconditions.checkNotNull(fs); Preconditions.checkNotNull(path); AclStatus aclStatus = null; try { aclStatus = fs.getAclStatus(path); } catch (AclException ex) { if (LOG.isTraceEnabled()) { LOG.trace("No ACLs retrieved, skipping ACLs check (HDFS will enforce ACLs)", ex); } } catch (UnsupportedOperationException ex) { if (LOG.isTraceEnabled()) LOG.trace("No ACLs retrieved, unsupported", ex); } return new Permissions(fs.getFileStatus(path), aclStatus); } /** * Returns the FsPermissionChecker singleton. */ public static FsPermissionChecker getInstance() { return instance_; } }