private void checkRangerPermission()

in hdfs-agent/src/main/java/org/apache/ranger/authorization/hadoop/RangerAccessControlEnforcer.java [110:468]


    private void checkRangerPermission(String fsOwner, String superGroup, UserGroupInformation ugi, INodeAttributes[] inodeAttrs, INode[] inodes,
            byte[][] pathByNameArr, int snapshotId, String path, int ancestorIndex, boolean doCheckOwner, FsAction ancestorAccess,
            FsAction parentAccess, FsAction access, FsAction subAccess, boolean ignoreEmptyDir, String operationName, CallerContext callerContext) throws AccessControlException {
        AuthzStatus  authzStatus  = AuthzStatus.NOT_DETERMINED;
        String       resourcePath = path;
        AuthzContext context      = new AuthzContext(ugi, operationName, access == null && parentAccess == null && ancestorAccess == null && subAccess == null);

        if (LOG.isDebugEnabled()) {
            LOG.debug("==> RangerAccessControlEnforcer.checkRangerPermission(fsOwner={}; superGroup={}, inodesCount={}, snapshotId={}, user={}, provided-path={}, ancestorIndex={}, doCheckOwner={}, ancestorAccess={}, parentAccess={}, access={}, subAccess={}, ignoreEmptyDir={}, operationName={}, callerContext={})",
                    fsOwner, superGroup, inodes != null ? inodes.length : 0, snapshotId, context.user, path, ancestorIndex, doCheckOwner,
                    ancestorAccess, parentAccess, access, subAccess, ignoreEmptyDir, operationName, callerContext);

            LOG.info("operationName={}, path={}, user={}, ancestorIndex={}, ancestorAccess={}, parentAccess={}, access={}, subAccess={}", context.operationName, path, context.user, ancestorIndex, ancestorAccess, parentAccess, access, subAccess);
        }

        OptimizedAuthzContext optAuthzContext = null;
        RangerPerfTracer      perf            = null;

        if (RangerPerfTracer.isPerfTraceEnabled(PERF_HDFSAUTH_REQUEST_LOG)) {
            perf = RangerPerfTracer.getPerfTracer(PERF_HDFSAUTH_REQUEST_LOG, "RangerHdfsAuthorizer.checkRangerPermission(provided-path=" + path + ")");
        }

        try {
            INode  ancestor     = null;
            INode  parent       = null;
            INode  inode        = null;
            String providedPath = path;

            boolean useDefaultAuthorizerOnly = false;
            boolean doNotGenerateAuditRecord = false;

            if (plugin != null && !ArrayUtils.isEmpty(inodes)) {
                int sz = inodeAttrs.length;

                LOG.trace("Size of INodeAttrs array:[{}]", sz);
                LOG.trace("Size of INodes array:[{}]", inodes.length);

                byte[][] components = new byte[sz][];
                int      i          = 0;

                for (; i < sz; i++) {
                    if (inodeAttrs[i] != null) {
                        components[i] = inodeAttrs[i].getLocalNameBytes();
                    } else {
                        break;
                    }
                }

                if (i != sz) {
                    LOG.trace("Input INodeAttributes array contains null at position {}", i);
                    LOG.trace("Will use only first [{}] components", i);
                }

                if (sz == 1 && inodes.length == 1 && inodes[0].getParent() != null) {
                    LOG.trace("Using the only inode in the array to figure out path to resource. No audit record will be generated for this authorization request");

                    doNotGenerateAuditRecord = true;

                    resourcePath = inodes[0].getFullPathName();

                    if (snapshotId != Snapshot.CURRENT_STATE_ID) {
                        useDefaultAuthorizerOnly = true;

                        LOG.trace("path:[{}] is for a snapshot, id=[{}], default Authorizer will be used to authorize this request", resourcePath, snapshotId);
                    } else {
                        LOG.trace("path:[{}] is not for a snapshot, id=[{}]. It will be used to authorize this request", resourcePath, snapshotId);
                    }
                } else {
                    if (snapshotId != Snapshot.CURRENT_STATE_ID) {
                        resourcePath = DFSUtil.byteArray2PathString(pathByNameArr);

                        LOG.trace("pathByNameArr array is used to figure out path to resource, resourcePath:[{}]", resourcePath);
                    } else {
                        resourcePath = DFSUtil.byteArray2PathString(components, 0, i);

                        LOG.trace("INodeAttributes array is used to figure out path to resource, resourcePath:[{}]", resourcePath);
                    }
                }

                if (ancestorIndex >= inodes.length) {
                    ancestorIndex = inodes.length - 1;
                }

                for (; ancestorIndex >= 0 && inodes[ancestorIndex] == null; ancestorIndex--) {
                    // empty
                }

                ancestor = inodes.length > ancestorIndex && ancestorIndex >= 0 ? inodes[ancestorIndex] : null;
                parent   = inodes.length > 1 ? inodes[inodes.length - 2] : null;
                inode    = inodes[inodes.length - 1]; // could be null while creating a new file

                /*
                    Check if optimization is done
                 */
                if (plugin.isAuthzOptimizationEnabled() && OperationOptimizer.isOptimizableOperation(operationName)) {
                    optAuthzContext = (new OperationOptimizer(this, operationName, resourcePath, ancestorAccess, parentAccess, access, subAccess, components, inodeAttrs, ancestorIndex, ancestor, parent, inode)).optimize();
                }

                if (optAuthzContext == OPT_BYPASS_AUTHZ) {
                    authzStatus = AuthzStatus.ALLOW;

                    return;
                } else if (optAuthzContext != null && optAuthzContext.authzStatus != null) {
                    authzStatus = optAuthzContext.authzStatus;

                    LOG.debug("OperationOptimizer.optimize() returned {}, operationName={} has been pre-computed. Returning without any access evaluation!", authzStatus, operationName);

                    if (authzStatus == AuthzStatus.ALLOW) {
                        return;
                    }

                    final FsAction action;

                    if (access != null) {
                        action = access;
                    } else if (parentAccess != null) {
                        action = parentAccess;
                    } else if (ancestorAccess != null) {
                        action = ancestorAccess;
                    } else {
                        action = FsAction.EXECUTE;
                    }

                    throw new RangerAccessControlException("Permission denied: user=" + context.user + ", access=" + action + ", inode=\"" + resourcePath + "\"");
                } else {
                    authzStatus = useDefaultAuthorizerOnly ? AuthzStatus.NOT_DETERMINED : AuthzStatus.ALLOW;
                }

                if (optAuthzContext != null) {
                    access         = optAuthzContext.access;
                    parentAccess   = optAuthzContext.parentAccess;
                    ancestorAccess = optAuthzContext.ancestorAccess;
                } else {
                    LOG.debug("OperationOptimizer.optimize() returned null, operationName={} needs to be evaluated!", operationName);
                }

                context.isTraverseOnlyCheck = parentAccess == null && ancestorAccess == null && access == null && subAccess == null;
                context.auditHandler        = doNotGenerateAuditRecord ? null : new RangerHdfsAuditHandler(providedPath, context.isTraverseOnlyCheck, plugin.getHadoopModuleName(), plugin.getExcludedUsers(), callerContext != null ? callerContext.toString() : null);

                /* Hadoop versions prior to 2.8.0 didn't ask for authorization of parent/ancestor traversal for
                 * reading or writing a file. However, Hadoop version 2.8.0 and later ask traversal authorization for
                 * such accesses. This means 2 authorization calls are made to the authorizer for a single access:
                 *  1. traversal authorization (where access, parentAccess, ancestorAccess and subAccess are null)
                 *  2. authorization for the requested permission (such as READ for reading a file)
                 *
                 * For the first call, Ranger authorizer would:
                 * - Deny traversal if Ranger policies explicitly deny EXECUTE access on the parent or closest ancestor
                 * - Else, allow traversal
                 *
                 * There are no changes to authorization of the second call listed above.
                 *
                 * This approach would ensure that Ranger authorization will continue to work with existing policies,
                 * without requiring policy migration/update, for the changes in behaviour in Hadoop 2.8.0.
                 */
                if (authzStatus == AuthzStatus.ALLOW && context.isTraverseOnlyCheck) {
                    authzStatus = traverseOnlyCheck(inode, inodeAttrs, resourcePath, components, parent, ancestor, ancestorIndex, context);
                }

                // checkStickyBit
                if (authzStatus == AuthzStatus.ALLOW && parentAccess != null && parentAccess.implies(FsAction.WRITE) && parent != null && inode != null) {
                    if (parent.getFsPermission() != null && parent.getFsPermission().getStickyBit()) {
                        // user should be owner of the parent or the inode
                        authzStatus = (StringUtils.equals(parent.getUserName(), context.user) || StringUtils.equals(inode.getUserName(), context.user)) ? AuthzStatus.ALLOW : AuthzStatus.NOT_DETERMINED;
                    }
                }

                // checkAncestorAccess
                if (authzStatus == AuthzStatus.ALLOW && ancestorAccess != null && ancestor != null) {
                    INodeAttributes ancestorAttribs = inodeAttrs.length > ancestorIndex ? inodeAttrs[ancestorIndex] : null;
                    String          ancestorPath    = ancestorAttribs != null ? DFSUtil.byteArray2PathString(components, 0, ancestorIndex + 1) : null;

                    authzStatus = isAccessAllowed(ancestor, ancestorAttribs, ancestorPath, ancestorAccess, context);
                    if (authzStatus == AuthzStatus.NOT_DETERMINED) {
                        authzStatus = checkDefaultEnforcer(fsOwner, superGroup, ugi, inodeAttrs, inodes, pathByNameArr, snapshotId, path, ancestorIndex, doCheckOwner,
                                ancestorAccess, null, null, null, ignoreEmptyDir, ancestor, parent, inode, context);
                    }
                }

                // checkParentAccess
                if (authzStatus == AuthzStatus.ALLOW && parentAccess != null && parent != null) {
                    INodeAttributes parentAttribs = inodeAttrs.length > 1 ? inodeAttrs[inodeAttrs.length - 2] : null;
                    String          parentPath    = parentAttribs != null ? DFSUtil.byteArray2PathString(components, 0, inodeAttrs.length - 1) : null;

                    authzStatus = isAccessAllowed(parent, parentAttribs, parentPath, parentAccess, context);
                    if (authzStatus == AuthzStatus.NOT_DETERMINED) {
                        authzStatus = checkDefaultEnforcer(fsOwner, superGroup, ugi, inodeAttrs, inodes, pathByNameArr, snapshotId, path, ancestorIndex, doCheckOwner,
                                null, parentAccess, null, null, ignoreEmptyDir, ancestor, parent, inode, context);
                    }
                }

                // checkINodeAccess
                if (authzStatus == AuthzStatus.ALLOW && access != null && inode != null) {
                    INodeAttributes inodeAttribs = inodeAttrs.length > 0 ? inodeAttrs[inodeAttrs.length - 1] : null;

                    authzStatus = isAccessAllowed(inode, inodeAttribs, resourcePath, access, context);
                    if (authzStatus == AuthzStatus.NOT_DETERMINED) {
                        authzStatus = checkDefaultEnforcer(fsOwner, superGroup, ugi, inodeAttrs, inodes, pathByNameArr, snapshotId, path, ancestorIndex, doCheckOwner,
                                null, null, access, null, ignoreEmptyDir, ancestor, parent, inode, context);
                    }
                }

                // checkSubAccess
                if (authzStatus == AuthzStatus.ALLOW && subAccess != null && inode != null && inode.isDirectory()) {
                    Stack<SubAccessData> directories = new Stack<>();

                    for (directories.push(new SubAccessData(inode.asDirectory(), resourcePath, inodes, inodeAttrs)); !directories.isEmpty(); ) {
                        SubAccessData       data  = directories.pop();
                        ReadOnlyList<INode> cList = data.dir.getChildrenList(snapshotId);

                        if (!(cList.isEmpty() && ignoreEmptyDir)) {
                            INodeAttributes dirAttribs = data.dir.getSnapshotINode(snapshotId);

                            authzStatus = isAccessAllowed(data.dir, dirAttribs, data.resourcePath, subAccess, context);

                            INodeDirectory    dirINode;
                            int               dirAncestorIndex;
                            INodeAttributes[] dirINodeAttrs;
                            INode[]           dirINodes;
                            INode             dirAncestor;
                            INode             dirParent;
                            byte[][]          dirComponents;

                            if (data.dir.equals(inode)) {
                                dirINode         = inode.asDirectory();
                                dirINodeAttrs    = inodeAttrs;
                                dirINodes        = inodes;
                                dirAncestorIndex = ancestorIndex;
                                dirAncestor      = ancestor;
                                dirParent        = parent;
                                dirComponents    = pathByNameArr;
                            } else {
                                INodeAttributes[] curINodeAttributes;
                                INode[]           curINodes;

                                dirINode           = data.dir;
                                curINodeAttributes = data.iNodeAttributes;
                                curINodes          = data.inodes;

                                int idx;

                                dirINodes = new INode[curINodes.length + 1];
                                for (idx = 0; idx < curINodes.length; idx++) {
                                    dirINodes[idx] = curINodes[idx];
                                }
                                dirINodes[idx] = dirINode;

                                dirINodeAttrs = new INodeAttributes[curINodeAttributes.length + 1];
                                for (idx = 0; idx < curINodeAttributes.length; idx++) {
                                    dirINodeAttrs[idx] = curINodeAttributes[idx];
                                }
                                dirINodeAttrs[idx] = dirAttribs;

                                for (dirAncestorIndex = dirINodes.length - 1; dirAncestorIndex >= 0 && dirINodes[dirAncestorIndex] == null; dirAncestorIndex--) {
                                    // empty
                                }

                                dirAncestor   = dirINodes.length > dirAncestorIndex && dirAncestorIndex >= 0 ? dirINodes[dirAncestorIndex] : null;
                                dirParent     = dirINodes.length > 1 ? dirINodes[dirINodes.length - 2] : null;
                                dirComponents = dirINode.getPathComponents();
                            }

                            if (authzStatus == AuthzStatus.NOT_DETERMINED && !plugin.isUseLegacySubAccessAuthorization()) {
                                if (LOG.isDebugEnabled()) {
                                    if (data.dir.equals(inode)) {
                                        LOG.debug("Top level directory being processed for default authorizer call, [{}]", data.resourcePath);
                                    } else {
                                        LOG.debug("Sub directory being processed for default authorizer call, [{}]", data.resourcePath);
                                    }

                                    LOG.debug("Calling default authorizer for hierarchy/subaccess with the following parameters");

                                    LOG.debug("fsOwner={}; superGroup={}, inodesCount={}, snapshotId={}, user={}, provided-path={}, ancestorIndex={}, doCheckOwner={}, ancestorAccess=null, parentAccess=null, access=null, subAccess=null, ignoreEmptyDir={}, operationName={}, callerContext=null",
                                            fsOwner, superGroup, dirINodes != null ? dirINodes.length : 0, snapshotId, ugi != null ? ugi.getShortUserName() : null,
                                            data.resourcePath, dirAncestorIndex, doCheckOwner, ignoreEmptyDir, operationName);
                                }
                                authzStatus = checkDefaultEnforcer(fsOwner, superGroup, ugi, dirINodeAttrs, dirINodes, dirComponents, snapshotId, data.resourcePath, dirAncestorIndex, doCheckOwner,
                                        null, null, null, null, ignoreEmptyDir, dirAncestor, dirParent, dirINode, context);

                                LOG.debug("Default authorizer call returned : [{}]", authzStatus);
                            }

                            if (authzStatus != AuthzStatus.ALLOW) {
                                break;
                            }

                            AuthzStatus subDirAuthStatus             = AuthzStatus.NOT_DETERMINED;
                            boolean     optimizeSubAccessAuthEnabled = plugin.isOptimizeSubAccessAuthEnabled();

                            if (optimizeSubAccessAuthEnabled) {
                                subDirAuthStatus = isAccessAllowedForHierarchy(data.dir, dirAttribs, data.resourcePath, subAccess, context);
                            }

                            if (subDirAuthStatus != AuthzStatus.ALLOW) {
                                for (INode child : cList) {
                                    if (child.isDirectory()) {
                                        if (data.resourcePath.endsWith(Path.SEPARATOR)) {
                                            directories.push(new SubAccessData(child.asDirectory(), data.resourcePath + child.getLocalName(), dirINodes, dirINodeAttrs));
                                        } else {
                                            directories.push(new SubAccessData(child.asDirectory(), data.resourcePath + Path.SEPARATOR_CHAR + child.getLocalName(), dirINodes, dirINodeAttrs));
                                        }
                                    }
                                }
                            }
                        }
                    }

                    if (authzStatus == AuthzStatus.NOT_DETERMINED) {
                        authzStatus = checkDefaultEnforcer(fsOwner, superGroup, ugi, inodeAttrs, inodes, pathByNameArr, snapshotId, path, ancestorIndex, doCheckOwner,
                                null, null, null, subAccess, ignoreEmptyDir, ancestor, parent, inode, context);
                    }
                }

                // checkOwnerAccess
                if (authzStatus == AuthzStatus.ALLOW && doCheckOwner) {
                    INodeAttributes inodeAttribs = inodeAttrs.length > 0 ? inodeAttrs[inodeAttrs.length - 1] : null;
                    String          owner        = inodeAttribs != null ? inodeAttribs.getUserName() : null;

                    authzStatus = StringUtils.equals(context.user, owner) ? AuthzStatus.ALLOW : AuthzStatus.NOT_DETERMINED;
                }
            }

            if (authzStatus == AuthzStatus.NOT_DETERMINED) {
                authzStatus = checkDefaultEnforcer(fsOwner, superGroup, ugi, inodeAttrs, inodes, pathByNameArr, snapshotId, path, ancestorIndex, doCheckOwner,
                        ancestorAccess, parentAccess, access, subAccess, ignoreEmptyDir, ancestor, parent, inode, context);
            }

            if (authzStatus != AuthzStatus.ALLOW) {
                FsAction action = access;

                if (action == null) {
                    if (parentAccess != null) {
                        action = parentAccess;
                    } else if (ancestorAccess != null) {
                        action = ancestorAccess;
                    } else {
                        action = FsAction.EXECUTE;
                    }
                }

                throw new RangerAccessControlException("Permission denied: user=" + context.user + ", access=" + action + ", inode=\"" + resourcePath + "\"");
            }
        } finally {
            if (context.auditHandler != null) {
                context.auditHandler.flushAudit();
            }

            if (optAuthzContext != null && optAuthzContext != OPT_BYPASS_AUTHZ) {
                if (LOG.isDebugEnabled()) {
                    LOG.debug("Updating OptimizedAuthzContext:[{}] with authzStatus={}]", optAuthzContext, authzStatus.name());
                }

                optAuthzContext.authzStatus = authzStatus;
            }

            RangerPerfTracer.log(perf);

            LOG.debug("<== RangerAccessControlEnforcer.checkRangerPermission({}, {}, user={}) : {}", resourcePath, access, context.user, authzStatus);
        }
    }