org.apache.impala.util.FsPermissionChecker.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.impala.util.FsPermissionChecker.java

Source

// 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_;
    }
}