function objectGetACL(authInfo, request, log, callback) {
    log.debug('processing request', { method: 'objectGetACL' });
    const bucketName = request.bucketName;
    const objectKey = request.objectKey;

    const decodedVidResult = decodeVersionId(request.query);
    if (decodedVidResult instanceof Error) {
        log.trace('invalid versionId query', {
            versionId: request.query.versionId,
            error: decodedVidResult,
        });
        return callback(decodedVidResult);
    }
    const versionId = decodedVidResult;

    // FIXME pass 'getDeleteMarker: true' option to set
    // 'x-amz-delete-marker' header (see S3C-7592)
    const metadataValParams = {
        authInfo,
        bucketName,
        objectKey,
        versionId,
        requestType: request.apiMethods || 'objectGetACL',
        request,
    };
    const grantInfo = {
        grants: [],
        ownerInfo: {
            ID: undefined,
            displayName: undefined,
        },
    };

    return async.waterfall([
        function validateBucketAndObj(next) {
            return standardMetadataValidateBucketAndObj(metadataValParams, request.actionImplicitDenies, log,
                (err, bucket, objectMD) => {
                    if (err) {
                        log.trace('request authorization failed',
                            { method: 'objectGetACL', error: err });
                        return next(err);
                    }
                    if (!objectMD) {
                        const err = versionId ? errors.NoSuchVersion :
                            errors.NoSuchKey;
                        log.trace('error processing request',
                            { method: 'objectGetACL', error: err });
                        return next(err, bucket);
                    }
                    if (objectMD.isDeleteMarker) {
                        if (versionId) {
                            log.trace('requested version is delete marker',
                                { method: 'objectGetACL' });
                            // FIXME we should return a `x-amz-delete-marker: true` header,
                            // see S3C-7592
                            return next(errors.MethodNotAllowed);
                        }
                        log.trace('most recent version is delete marker',
                            { method: 'objectGetACL' });
                        // FIXME we should return a `x-amz-delete-marker: true` header,
                        // see S3C-7592
                        return next(errors.NoSuchKey);
                    }
                    return next(null, bucket, objectMD);
                });
        },
        function gatherACLs(bucket, objectMD, next) {
            const verCfg = bucket.getVersioningConfiguration();
            const resVersionId = getVersionIdResHeader(verCfg, objectMD);
            const objectACL = objectMD.acl;
            grantInfo.ownerInfo.ID = objectMD['owner-id'];
            grantInfo.ownerInfo.displayName = objectMD['owner-display-name'];
            // Object owner always has full control
            const ownerGrant = {
                ID: objectMD['owner-id'],
                displayName: objectMD['owner-display-name'],
                permission: 'FULL_CONTROL',
            };
            if (objectACL.Canned !== '') {
                /**
                * If bucket owner and object owner are different
                * need to send info about bucket owner from bucket
                * metadata to handleCannedGrant function
                */
                let cannedGrants;
                if (bucket.getOwner() !== objectMD['owner-id']) {
                    cannedGrants = aclUtils.handleCannedGrant(
                        objectACL.Canned, ownerGrant, bucket);
                } else {
                    cannedGrants = aclUtils.handleCannedGrant(
                        objectACL.Canned, ownerGrant);
                }
                grantInfo.grants = grantInfo.grants.concat(cannedGrants);
                const xml = aclUtils.convertToXml(grantInfo);
                return next(null, bucket, xml, resVersionId);
            }
            /**
            * Build array of all canonicalIDs used in ACLs so duplicates
            * will be retained (e.g. if an account has both read and write
            * privileges, want to display both and not lose the duplicate
            * when receive one dictionary entry back from Vault)
            */
            const canonicalIDs = aclUtils.getCanonicalIDs(objectACL);
            // Build array with grants by URI
            const uriGrantInfo = aclUtils.getUriGrantInfo(objectACL);

            if (canonicalIDs.length === 0) {
                /**
                * If no acl's set by account canonicalID, just add URI
                * grants (if any) and return
                */
                grantInfo.grants = grantInfo.grants.concat(uriGrantInfo);
                const xml = aclUtils.convertToXml(grantInfo);
                return next(null, bucket, xml, resVersionId);
            }
            /**
            * If acl's set by account canonicalID,
            * get emails from Vault to serve
            * as display names
            */
            return vault.getEmailAddresses(canonicalIDs, log, (err, emails) => {
                if (err) {
                    log.trace('error processing request',
                        { method: 'objectGetACL', error: err });
                    return next(err, bucket);
                }
                const individualGrants = aclUtils.getIndividualGrants(objectACL,
                    canonicalIDs, emails);
                // Add to grantInfo any individual grants and grants by uri
                grantInfo.grants = grantInfo.grants
                    .concat(individualGrants).concat(uriGrantInfo);
                // parse info about accounts and owner info to convert to xml
                const xml = aclUtils.convertToXml(grantInfo);
                return next(null, bucket, xml, resVersionId);
            });
        },
    ], (err, bucket, xml, resVersionId) => {
        const resHeaders = collectCorsHeaders(request.headers.origin,
            request.method, bucket);
        if (err) {
            monitoring.promMetrics(
                    'GET', bucketName, err.code, 'getObjectAcl');
            return callback(err, null, resHeaders);
        }
        pushMetric('getObjectAcl', log, {
            authInfo,
            bucket: bucketName,
            keys: [objectKey],
            versionId: resVersionId,
            location: bucket ? bucket.getLocationConstraint() : undefined,
        });
        monitoring.promMetrics('GET', bucketName, '200', 'getObjectAcl');
        resHeaders['x-amz-version-id'] = resVersionId;
        return callback(null, xml, resHeaders);
    });
}
objectGetACL - Return ACL for object
@param {AuthInfo} authInfo - Instance of AuthInfo class with requester's info
@param {object} request - http request object
@param {object} log - Werelogs logger
@param {function} callback - callback to respond to http request
@return {undefined}