<commit_before>/*
 * Copyright 2012 Sakai Foundation (SF) Licensed under the
 * Educational Community 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.osedu.org/licenses/ECL-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.
 */

var rolesAPI = require('oae-roles/lib/api');
var principalsAPI = require('oae-principals/lib/api');

/**
 * Determine whether or not the principal specified by principalUuid can perform the action on the given resourceUuid
 * 
 * @param {String}                   principalUuid      The principal to check
 * @param {String}                   action             The action to check
 * @param {String}                   resourceUuid       The resource to check
 * @param {Function(err, isAllowed)} callback           A function specifying whether or not the user can perform the operation
 * @param {Object}                   callback.err       An error that occured, if any
 * @param {Boolean}                  callback.isAllowed Whether or not the principal can perform the operation on the resource
 */
module.exports.isAllowed = function(principalUuid, action, resourceUuid, callback) {
    // first check this principal directly
    rolesAPI.hasRole(principalUuid, resourceUuid, action, function(err, hasRole) {
        if (!err) {
            if (hasRole) {
                return callback(null, true);
            } else {
                // get the entire (indirect as well) group ancestry of the principal and check if they have the access

                // TODO: There is a potential optimization here to not aggressively get all ancestors, but rather check one level at a time
                // We can do the above optimization assuming we can short-circuit the checks when we get one positive check.
                principalsAPI.memberOf(principalUuid, false, null, null, function(err, groupUuids) {
                    if (!err) {
                        return internalIsAllowed(groupUuids, action, resourceUuid, callback);
                    } else {
                        return callback(err);
                    }
                });
            }
        } else {
            return callback(err);
        }
    });
}

/**
 * Determine whether or not the principal specified by principalUuid can perform at least one action on the given resourceUuid

 * @param {String}                   principalUuid      The principal to check
 * @param {String}                   resourceUuid       The resourceUuid to check
 * @param {Function(err, isAllowed)} callback           A function specifying whether or not the user can perform any operation
 * @param {Object}                   callback.err       An error that occured, if any
 * @param {Boolean}                  callback.isAllowed Whether or not the principal is authorized to perform any action on the resource
 */
module.exports.isAllowedAny = function(principalUuid, resourceUuid, callback) {
    // first check the principal directly
    rolesAPI.getRole(principalUuid, resourceUuid, function(err, role) {
        if (!err) {
            if (role) {
                // there was a role, short-circuit and indicate that the user has some role on the resource
                return callback(null, true);
            } else {
                principalsAPI.memberOf(principalUuid, false, null, null, function(err, groupUuids) {
                    if (!err) {
                        return internalIsAllowed(groupUuids, null, resourceUuid, callback);
                    } else {
                        return callback(err);
                    }
                });
            }
        } else {
            return callback(err);
        }
    });
}

/**
 * Determine all the permissions the principal has on the resource specified by resourceUuid. Permissions may have been granted directly on the
 * principal, or indirectly through group membership.
 *
 * @param {String}                      principalUuid        The UUID of the principal
 * @param {Function(err, permissions)}  callback             The callback function, invoked when the process has completed
 * @param {Object}                      callback.err         An error that occured, if any
 * @param {Array<String>}               callback.permissions All the permissions that the principal has on the resource either directly or through group membership
 */
 module.exports.getAllPermissions = function(principalUuid, resourceUuid, callback) {

    // variables for monitoring the status of asynchronous role checks
    var permissions = {};
    var index = 0;
    var numPrincipals = -1;
    var errResult = false;

    // aggregatePermissions monitors the progress of asynchronous aggregation of permissions on the resource and calls back when appropriate
    var aggregatePermissions = function(err, role) {
        if (errResult) {
            // do nothing, there was an err while checking permissions and we've already called back
        } else if (err) {
            // we've received an error, record it and we can call back immediately
            errResult = err;
            return callback(errResult);
        } else {
            index++;

            if (role) {
                // We simply add the role as the permission for now. In the future if we start association actual permissions to roles, we will
                // need to grab the actions that are assigned to the role for the resource instead.
                permissions[role] = true;
            }

            if (index >= numPrincipals) {
                // we've successfully aggregated all potential permissions, call back with them
                return callback(null, Object.keys(permissions));
            }
        }
    }

    // aggregate all roles for all group ancestors.
    principalsAPI.memberOf(principalUuid, false, null, null, function(err, groupUuids) {
        if (!err) {
            groupUuids.push(principalUuid);
            numPrincipals = groupUuids.length;
            groupUuids.forEach(function(groupUuid) {
                rolesAPI.getRole(groupUuid, resourceUuid, aggregatePermissions);
            });
        } else {
            callback(err);
        }
    });
 }

/**
 * Determine whether or not one of the principals in the array of principalUuids is able to perform the action on the
 * resource specified. If the action is null, then we will determine if the user can perform *any* action on the
 * resource specified.
 *
 * @param {Array<String>}            principalUuids     The array of principals to check
 * @param {String}                   action             The action to check. If null, check for any action.
 * @param {String}                   resourceUuid       The resource to check
 * @param {Function(err, isAllowed)} callback           A function invoked when the process is complete.
 * @param {Object}                   callback.err       An error that occured, if any
 * @param {Boolean}                  callback.isAllowed Whether or not one of the principals is authorized to perform the action
 */
var internalIsAllowed = function(principalUuids, action, resourceUuid, callback) {<commit_msg>(Issue #45) Changing 'groupUuids' to 'principalUuids' to better reflect the contents of an array<commit_after>/*
 * Copyright 2012 Sakai Foundation (SF) Licensed under the
 * Educational Community 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.osedu.org/licenses/ECL-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.
 */

var rolesAPI = require('oae-roles/lib/api');
var principalsAPI = require('oae-principals/lib/api');

/**
 * Determine whether or not the principal specified by principalUuid can perform the action on the given resourceUuid
 * 
 * @param {String}                   principalUuid      The principal to check
 * @param {String}                   action             The action to check
 * @param {String}                   resourceUuid       The resource to check
 * @param {Function(err, isAllowed)} callback           A function specifying whether or not the user can perform the operation
 * @param {Object}                   callback.err       An error that occured, if any
 * @param {Boolean}                  callback.isAllowed Whether or not the principal can perform the operation on the resource
 */
module.exports.isAllowed = function(principalUuid, action, resourceUuid, callback) {
    // first check this principal directly
    rolesAPI.hasRole(principalUuid, resourceUuid, action, function(err, hasRole) {
        if (!err) {
            if (hasRole) {
                return callback(null, true);
            } else {
                // get the entire (indirect as well) group ancestry of the principal and check if they have the access

                // TODO: There is a potential optimization here to not aggressively get all ancestors, but rather check one level at a time
                // We can do the above optimization assuming we can short-circuit the checks when we get one positive check.
                principalsAPI.memberOf(principalUuid, false, null, null, function(err, groupUuids) {
                    if (!err) {
                        return internalIsAllowed(groupUuids, action, resourceUuid, callback);
                    } else {
                        return callback(err);
                    }
                });
            }
        } else {
            return callback(err);
        }
    });
}

/**
 * Determine whether or not the principal specified by principalUuid can perform at least one action on the given resourceUuid

 * @param {String}                   principalUuid      The principal to check
 * @param {String}                   resourceUuid       The resourceUuid to check
 * @param {Function(err, isAllowed)} callback           A function specifying whether or not the user can perform any operation
 * @param {Object}                   callback.err       An error that occured, if any
 * @param {Boolean}                  callback.isAllowed Whether or not the principal is authorized to perform any action on the resource
 */
module.exports.isAllowedAny = function(principalUuid, resourceUuid, callback) {
    // first check the principal directly
    rolesAPI.getRole(principalUuid, resourceUuid, function(err, role) {
        if (!err) {
            if (role) {
                // there was a role, short-circuit and indicate that the user has some role on the resource
                return callback(null, true);
            } else {
                principalsAPI.memberOf(principalUuid, false, null, null, function(err, groupUuids) {
                    if (!err) {
                        return internalIsAllowed(groupUuids, null, resourceUuid, callback);
                    } else {
                        return callback(err);
                    }
                });
            }
        } else {
            return callback(err);
        }
    });
}

/**
 * Determine all the permissions the principal has on the resource specified by resourceUuid. Permissions may have been granted directly on the
 * principal, or indirectly through group membership.
 *
 * @param {String}                      principalUuid        The UUID of the principal
 * @param {Function(err, permissions)}  callback             The callback function, invoked when the process has completed
 * @param {Object}                      callback.err         An error that occured, if any
 * @param {Array<String>}               callback.permissions All the permissions that the principal has on the resource either directly or through group membership
 */
 module.exports.getAllPermissions = function(principalUuid, resourceUuid, callback) {

    // variables for monitoring the status of asynchronous role checks
    var permissions = {};
    var index = 0;
    var numPrincipals = -1;
    var errResult = false;

    // aggregatePermissions monitors the progress of asynchronous aggregation of permissions on the resource and calls back when appropriate
    var aggregatePermissions = function(err, role) {
        if (errResult) {
            // do nothing, there was an err while checking permissions and we've already called back
        } else if (err) {
            // we've received an error, record it and we can call back immediately
            errResult = err;
            return callback(errResult);
        } else {
            index++;

            if (role) {
                // We simply add the role as the permission for now. In the future if we start association actual permissions to roles, we will
                // need to grab the actions that are assigned to the role for the resource instead.
                permissions[role] = true;
            }

            if (index >= numPrincipals) {
                // we've successfully aggregated all potential permissions, call back with them
                return callback(null, Object.keys(permissions));
            }
        }
    }

    // aggregate all roles for all group ancestors.
    principalsAPI.memberOf(principalUuid, false, null, null, function(err, principalUuids) {
        if (!err) {
            principalUuids.push(principalUuid);
            numPrincipals = principalUuids.length;
            principalUuids.forEach(function(principalUuid) {
                rolesAPI.getRole(principalUuid, resourceUuid, aggregatePermissions);
            });
        } else {
            callback(err);
        }
    });
 }

/**
 * Determine whether or not one of the principals in the array of principalUuids is able to perform the action on the
 * resource specified. If the action is null, then we will determine if the user can perform *any* action on the
 * resource specified.
 *
 * @param {Array<String>}            principalUuids     The array of principals to check
 * @param {String}                   action             The action to check. If null, check for any action.
 * @param {String}                   resourceUuid       The resource to check
 * @param {Function(err, isAllowed)} callback           A function invoked when the process is complete.
 * @param {Object}                   callback.err       An error that occured, if any
 * @param {Boolean}                  callback.isAllowed Whether or not one of the principals is authorized to perform the action
 */
var internalIsAllowed = function(principalUuids, action, resourceUuid, callback) {
{'fraction_non_alphanumeric': 0.06919569096556738, 'fraction_numerical': 0.0014539686735840329, 'mean_word_length': 2.151187005414411, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 9, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3002487', 'n_tokens_mistral': 3624, 'n_tokens_neox': 3518, 'n_words': 1521}