Skip to content

Commit

Permalink
CLDSRV-427: Improving functions using helper function
Browse files Browse the repository at this point in the history
- In this commit , I added a helper (processBucketPolicy) function
for the bycket policies checks that are shared between the
isbucketAuthorized, isObjAuthorized and evaluateBucketPolicyWithIAM
for a better code readability and to avoid long functions.
  • Loading branch information
benzekrimaha committed Nov 10, 2023
1 parent 6fb225e commit 6919ca7
Showing 1 changed file with 135 additions and 103 deletions.
238 changes: 135 additions & 103 deletions lib/api/apiUtils/authorization/permissionChecks.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
const { evaluators, actionMaps, RequestContext } = require('arsenal').policies;
const constants = require('../../../../constants');

const { allAuthedUsersId, bucketOwnerActions, logId, publicId,
assumedRoleArnResourceType, backbeatLifecycleSessionName, arrayOfAllowed } = constants;
const {
allAuthedUsersId, bucketOwnerActions, logId, publicId,
assumedRoleArnResourceType, backbeatLifecycleSessionName, arrayOfAllowed,
} = constants;

// whitelist buckets to allow public read on objects
const publicReadBuckets = process.env.ALLOW_PUBLIC_READ_BUCKETS ?
process.env.ALLOW_PUBLIC_READ_BUCKETS.split(',') : [];
const publicReadBuckets = process.env.ALLOW_PUBLIC_READ_BUCKETS
? process.env.ALLOW_PUBLIC_READ_BUCKETS.split(',') : [];

function checkBucketAcls(bucket, requestType, canonicalID, mainApiCall) {
// Same logic applies on the Versioned APIs, so let's simplify it.
const requestTypeParsed = requestType.endsWith('Version') ?
requestType.slice(0, 'Version'.length * -1) : requestType;
const requestTypeParsed = requestType.endsWith('Version')
? requestType.slice(0, -7) : requestType;
if (bucket.getOwner() === canonicalID) {
return true;
}
Expand Down Expand Up @@ -95,8 +97,8 @@ function checkBucketAcls(bucket, requestType, canonicalID, mainApiCall) {
// objectPutACL, objectGetACL, objectHead or objectGet, the bucket
// authorization check should just return true so can move on to check
// rights at the object level.
return (requestTypeParsed === 'objectPutACL' || requestTypeParsed === 'objectGetACL' ||
requestTypeParsed === 'objectGet' || requestTypeParsed === 'objectHead');
return (requestTypeParsed === 'objectPutACL' || requestTypeParsed === 'objectGetACL'
|| requestTypeParsed === 'objectGet' || requestTypeParsed === 'objectHead');
}

function checkObjectAcls(bucket, objectMD, requestType, canonicalID, requesterIsNotUser,
Expand Down Expand Up @@ -188,9 +190,9 @@ function checkObjectAcls(bucket, objectMD, requestType, canonicalID, requesterIs
// allow public reads on buckets that are whitelisted for anonymous reads
// TODO: remove this after bucket policies are implemented
const bucketAcl = bucket.getAcl();
const allowPublicReads = publicReadBuckets.includes(bucket.getName()) &&
bucketAcl.Canned === 'public-read' &&
(requestType === 'objectGet' || requestType === 'objectHead');
const allowPublicReads = publicReadBuckets.includes(bucket.getName())
&& bucketAcl.Canned === 'public-read'
&& (requestType === 'objectGet' || requestType === 'objectHead');
if (allowPublicReads) {
return true;
}
Expand Down Expand Up @@ -287,120 +289,151 @@ function checkBucketPolicy(policy, requestType, canonicalID, arn, bucketOwner, l
return permission;
}

function isAuthorized(method, bucket, objectMD, requestTypes, canonicalID, authInfo, log, request,
actionImplicitDenies, withIam) {
if (!withIam) {
function processBucketPolicy(requestType, bucket, canonicalID, arn, bucketOwner, log,
request, aclPermission, results, actionImplicitDenies) {
const bucketPolicy = bucket.getBucketPolicy();

if (!bucketPolicy) {
// eslint-disable-next-line no-param-reassign
withIam = false;
results[requestType] = actionImplicitDenies[requestType] === false && aclPermission;
} else {
const bucketPolicyPermission = checkBucketPolicy(bucketPolicy, requestType, canonicalID, arn,
bucketOwner, log, request);

if (bucketPolicyPermission === 'explicitDeny') {
// eslint-disable-next-line no-param-reassign
results[requestType] = false;
} else if (bucketPolicyPermission === 'allow') {
// eslint-disable-next-line no-param-reassign
results[requestType] = true;
} else {
// eslint-disable-next-line no-param-reassign
results[requestType] = actionImplicitDenies[requestType] === false && aclPermission;
}
}
return results[requestType];
}

function isBucketAuthorized(bucket, requestTypes, canonicalID, authInfo, log, request, actionImplicitDenies = {}) {
if (!Array.isArray(requestTypes)) {
// eslint-disable-next-line no-param-reassign
requestTypes = [requestTypes];
}
// By default, all missing actions are defined as allowed from IAM, to be
// backward compatible
if (!actionImplicitDenies) {
// eslint-disable-next-line no-param-reassign
actionImplicitDenies = {};
}
requestTypes.forEach(requestType => {
if (actionImplicitDenies[requestType] === undefined) {
const mainApiCall = requestTypes[0];
const results = {};
return requestTypes.every(_requestType => {
// By default, all missing actions are defined as allowed from IAM, to be
// backward compatible
if (actionImplicitDenies[_requestType] === undefined) {
// eslint-disable-next-line no-param-reassign
actionImplicitDenies[requestType] = false;
actionImplicitDenies[_requestType] = false;
}
// Check to see if user is authorized to perform a
// particular action on bucket based on ACLs.
// TODO: Add IAM checks
let requesterIsNotUser = true;
let arn = null;
if (authInfo) {
requesterIsNotUser = !authInfo.isRequesterAnIAMUser();
arn = authInfo.getArn();
}
// if the bucket owner is an account, users should not have default access
if ((bucket.getOwner() === canonicalID) && requesterIsNotUser) {
results[_requestType] = actionImplicitDenies[_requestType] === false;
return results[_requestType];
}
const aclPermission = checkBucketAcls(bucket, _requestType, canonicalID, mainApiCall);
return processBucketPolicy(_requestType, bucket, canonicalID, arn, bucket.getOwner(), log,
request, aclPermission, results, actionImplicitDenies);
});
}

function evaluateBucketPolicyWithIAM(bucket, requestTypes, canonicalID, authInfo, actionImplicitDenies = {},
log, request) {
if (!Array.isArray(requestTypes)) {
// eslint-disable-next-line no-param-reassign
requestTypes = [requestTypes];
}
const results = {};
return requestTypes.every(_requestType => {
// By default, all missing actions are defined as allowed from IAM, to be
// backward compatible
if (actionImplicitDenies[_requestType] === undefined) {
// eslint-disable-next-line no-param-reassign
actionImplicitDenies[_requestType] = false;
}
let arn = null;
if (authInfo) {
arn = authInfo.getArn();
}
return processBucketPolicy(_requestType, bucket, canonicalID, arn, bucket.getOwner(), log,
request, true, results, actionImplicitDenies);
});
}

function isObjAuthorized(bucket, objectMD, requestTypes, canonicalID, authInfo, log, request,
actionImplicitDenies = {}) {
if (!Array.isArray(requestTypes)) {
// eslint-disable-next-line no-param-reassign
requestTypes = [requestTypes];
}
// By default, all missing actions are defined as allowed from IAM, to be
// backward compatible
const results = {};
const mainApiCall = requestTypes[0];
requestTypes.forEach(_requestType => {
const parsedMethodName = _requestType.endsWith('Version') ? _requestType.slice(0, -7) : _requestType;
return requestTypes.every(_requestType => {
// By default, all missing actions are defined as allowed from IAM, to be
// backward compatible
if (actionImplicitDenies[_requestType] === undefined) {
// eslint-disable-next-line no-param-reassign
actionImplicitDenies[_requestType] = false;
}
const parsedMethodName = _requestType.endsWith('Version')
? _requestType.slice(0, -7) : _requestType;
const bucketOwner = bucket.getOwner();
let requesterIsNotUser = false;
let arn;
if (!objectMD) {
// User is already authorized on the bucket for FULL_CONTROL or WRITE or
// bucket has canned ACL public-read-write
if (parsedMethodName === 'objectPut' || parsedMethodName === 'objectDelete') {
results[_requestType] = actionImplicitDenies[_requestType] === false;
return results[_requestType];
}
// check bucket has read access
// 'bucketGet' covers listObjects and listMultipartUploads, bucket read actions
results[_requestType] = isBucketAuthorized(bucket, 'bucketGet', canonicalID, authInfo, log, request);
return results[_requestType];
}
let requesterIsNotUser = true;
let arn = null;
let isUserUnauthenticated = false;
if (authInfo) {
requesterIsNotUser = !authInfo.isRequesterAnIAMUser();
arn = authInfo.getArn();
isUserUnauthenticated = arn === undefined;
}
if (method === 'isBucketAuthorized') {
const isBucketOwner = bucketOwner === canonicalID;
if (isBucketOwner && requesterIsNotUser && !withIam) {
results[_requestType] = actionImplicitDenies[_requestType] === false;
return;
}
} else {
if (!objectMD) {
// User is already authorized on the bucket for FULL_CONTROL or WRITE or
// bucket has canned ACL public-read-write
if (parsedMethodName === 'objectPut' || parsedMethodName === 'objectDelete') {
results[_requestType] = actionImplicitDenies[_requestType] === false;
return;
}
// check bucket has read access
// 'bucketGet' covers listObjects and listMultipartUploads, bucket read actions
results[_requestType] = isAuthorized('isBucketAuthorized', bucket, null, 'bucketGet', canonicalID,
authInfo, log, request, false);
return;
}
if (objectMD['owner-id'] === canonicalID && requesterIsNotUser) {
results[_requestType] = actionImplicitDenies[_requestType] === false;
return;
}
// account is authorized if:
// - requesttype is included in bucketOwnerActions and
// - account is the bucket owner
// - requester is account, not user
if (bucketOwnerActions.includes(parsedMethodName)
&& (bucketOwner === canonicalID)
&& requesterIsNotUser) {
results[_requestType] = actionImplicitDenies[_requestType] === false;
return;
}
}
const aclPermission = method === 'isBucketAuthorized' ?
checkBucketAcls(bucket, _requestType, canonicalID, mainApiCall) :
checkObjectAcls(bucket, objectMD, parsedMethodName, canonicalID,
requesterIsNotUser, isUserUnauthenticated, mainApiCall);
const bucketPolicy = bucket.getBucketPolicy();
if (!bucketPolicy) {
results[_requestType] = withIam === true ? actionImplicitDenies[_requestType] === false :
actionImplicitDenies[_requestType] === false && aclPermission;
return;
}
const bucketPolicyPermission = checkBucketPolicy(bucketPolicy, _requestType, canonicalID,
arn, bucketOwner, log, request);

if (bucketPolicyPermission === 'explicitDeny') {
results[_requestType] = false;
return;
if (objectMD['owner-id'] === canonicalID && requesterIsNotUser) {
results[_requestType] = actionImplicitDenies[_requestType] === false;
return results[_requestType];
}
// If the bucket policy returns an allow, we accept the request, as the
// IAM response here is either Allow or implicit deny.
if (bucketPolicyPermission === 'allow') {
results[_requestType] = true;
return;
// account is authorized if:
// - requesttype is included in bucketOwnerActions and
// - account is the bucket owner
// - requester is account, not user
if (bucketOwnerActions.includes(parsedMethodName)
&& (bucketOwner === canonicalID)
&& requesterIsNotUser) {
results[_requestType] = actionImplicitDenies[_requestType] === false;
return results[_requestType];
}
// eslint-disable-next-line max-len
results[_requestType] = withIam === true ? actionImplicitDenies[_requestType] === false :
actionImplicitDenies[_requestType] === false && aclPermission;
const aclPermission = checkObjectAcls(bucket, objectMD, parsedMethodName,
canonicalID, requesterIsNotUser, isUserUnauthenticated, mainApiCall);
return processBucketPolicy(_requestType, bucket, canonicalID, arn, bucketOwner,
log, request, aclPermission, results, actionImplicitDenies);
});
return Object.values(results).every(result => result === true);
}

function isBucketAuthorized(bucket, requestTypes, canonicalID, authInfo, log, request, actionImplicitDenies) {
return isAuthorized('isBucketAuthorized', bucket, null, requestTypes, canonicalID, authInfo, log,
request, actionImplicitDenies);
}

function evaluateBucketPolicyWithIAM(bucket, requestTypes, canonicalID, authInfo, log, request, actionImplicitDenies) {
return isAuthorized('isBucketAuthorized', bucket, null, requestTypes, canonicalID, authInfo, log,
request, actionImplicitDenies, true);
}

function isObjAuthorized(bucket, objectMD, requestTypes, canonicalID, authInfo, log, request, actionImplicitDenies) {
return isAuthorized('isObjAuthorized', bucket, objectMD, requestTypes, canonicalID, authInfo, log,
request, actionImplicitDenies);
}

function _checkResource(resource, bucketArn) {
Expand Down Expand Up @@ -447,9 +480,9 @@ function isLifecycleSession(arn) {
const resourceType = resourceNames[0];
const sessionName = resourceNames[resourceNames.length - 1];

return (service === 'sts' &&
resourceType === assumedRoleArnResourceType &&
sessionName === backbeatLifecycleSessionName);
return (service === 'sts'
&& resourceType === assumedRoleArnResourceType
&& sessionName === backbeatLifecycleSessionName);
}

module.exports = {
Expand All @@ -461,4 +494,3 @@ module.exports = {
isLifecycleSession,
evaluateBucketPolicyWithIAM,
};

0 comments on commit 6919ca7

Please sign in to comment.