API Docs for: 0.8.0
Show:

File: include/access_management.js

/*
    Copyright (C) 2016  PencilBlue, LLC

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
'use strict';

//dependencies
var crypto = require('crypto');
var util   = require('./util.js');

module.exports = function(pb) {

    /**
     * Service for managing user access
     *
     * @module Services
     * @submodule Security
     * @class SecurityService
     * @constructor
     */
    function SecurityService(){}

    /**
     *
     * @static
     * @readonly
     * @property ACCESS_USER
     * @type {Integer}
     */
    SecurityService.ACCESS_USER = 0;

    /**
     *
     * @static
     * @readonly
     * @property ACCESS_WRITER
     * @type {Integer}
     */
    SecurityService.ACCESS_WRITER = 1;

    /**
     *
     * @static
     * @readonly
     * @property ACCESS_EDITOR
     * @type {Integer}
     */
    SecurityService.ACCESS_EDITOR = 2;

    /**
     *
     * @static
     * @readonly
     * @property ACCESS_MANAGING_EDITOR
     * @type {Integer}
     */
    SecurityService.ACCESS_MANAGING_EDITOR = 3;

    /**
     *
     * @static
     * @readonly
     * @property ACCESS_ADMINISTRATOR
     * @type {Integer}
     */
    SecurityService.ACCESS_ADMINISTRATOR = 4;

    /**
     * Provides an array of all of the system roles.  It provides their numeric value as well as the unique string key
     * @static
     * @readonly
     * @property SYSTEM_ROLES
     * @type {Array}
     */
    SecurityService.SYSTEM_ROLES = Object.freeze([
        {
            key: 'ACCESS_USER',
            localizationKey: 'generic.ACCESS_USER',
            value: 0
        },
        {
            key: 'ACCESS_WRITER',
            localizationKey: 'generic.ACCESS_WRITER',
            value: 1
        },
        {
            key: 'ACCESS_EDITOR',
            localizationKey: 'generic.ACCESS_EDITOR',
            value: 2
        },
        {
            key: 'ACCESS_MANAGING_EDITOR',
            localizationKey: 'generic.ACCESS_MANAGING_EDITOR',
            value: 3
        },
        {
            key: 'ACCESS_ADMINISTRATOR',
            localizationKey: 'generic.ACCESS_ADMINISTRATOR',
            value: 4
        }
    ]);

    /**
     *
     * @private
     * @static
     * @readonly
     * @property PASSWORD_CHARS
     * @type {Array}
     */
    var PASSWORD_CHARS = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '!', '@', '#', '$', '%', '^', '&', '*', '?'];

    /**
     *
     * @private
     * @static
     * @readonly
     * @property ROLE_VAL_TO_NAME
     * @type {Array}
     */
    var ROLE_VAL_TO_NAME = SecurityService.SYSTEM_ROLES.reduce(function(prev, curr) {
        prev[curr.value] = curr.key;
        return prev;
    }, {});

    /**
     *
     * @static
     * @readonly
     * @property ACCESS_
     * @type {Integer}
     */
    SecurityService.AUTHENTICATED = 'authenticated';

    /**
     *
     * @static
     * @readonly
     * @property ACCESS_
     * @type {Integer}
     */
    SecurityService.ADMIN_LEVEL = 'admin_level';

    /**
     * Retrieves the localized names of access levels as an array
     *
     * @method getRoleNames
     * @param {Localization} ls The localization service
     * @return {Array}
     */
    SecurityService.getRoleNames = function(ls) {
        var map = SecurityService.getRoleToDisplayNameMap(ls);
        return util.hashToArray(map);
    };

    /**
     * Provides a hash of the default roles to their translated display name
     * @static
     * @method getRoleToDisplayNameMap
     * @param {Localization} ls
     * @return {Object}
     */
    SecurityService.getRoleToDisplayNameMap = function(ls) {
        if (util.isNullOrUndefined(ls)) {
            throw new Error('The localization parameter cannot be null');
        }

        return SecurityService.SYSTEM_ROLES.reduce(function(prev, curr) {
            prev[curr.key] = ls.g(curr.localizationKey);
            return prev;
        }, {});
    };

    /**
     * Returns the constant name of an access level number
     *
     * @method getRoleName
     * @param {Number} accessLevel
     */
    SecurityService.getRoleName = function(accessLevel) {
        var val = ROLE_VAL_TO_NAME[accessLevel];
        if (!val) {
            throw new Error(util.format("An invalid access level [%s] was provided", accessLevel));
        }
        return val;
    };

    /**
     * Authenticates a session
     * @static
     * @method authenticateSession
     * @param {Object} session
     * @param {Object} options
     * @param {Authentication} authenticator
     * @param {Function} cb
     */
    SecurityService.authenticateSession = function(session, options, authenticator, cb){
        var doAuthentication = function(session, options, authenticator, cb) {
            authenticator.authenticate(options, function(err, user) {
                if (util.isError(err) || !util.isObject(user)) {
                    return cb(err, user);
                }

                //remove password from data to be cached
                delete user.password;

                //build out session object
                user.permissions                   = pb.PluginService.getPermissionsForRole(user.admin);
                session.authentication.user        = user;
                session.authentication.user_id     = user[pb.DAO.getIdField()].toString();
                session.authentication.admin_level = user.admin;

                //set locale if no preference already indicated for the session
                if (!session.locale) {
                    session.locale = user.locale;
                }
                cb(null, user);
            });
        };
        doAuthentication(session, options, authenticator, cb);
    };

    /**
     * Check to see if a user meets security requirements
     * @static
     * @method isAuthorized
     * @param {Object} session      [description]
     * @param {Object} requirements Object containing access requirements
     */
    SecurityService.isAuthorized = function(session, requirements) {

        //check if authentication is required
        if (requirements[SecurityService.AUTHENTICATED]) {
            if (session.authentication.user_id === null) {
                return false;
            }
        }

        //check for admin access level
        if (requirements[SecurityService.ADMIN_LEVEL] !== undefined) {
            if (session.authentication.admin_level < requirements[SecurityService.ADMIN_LEVEL]) {
                return false;
            }
        }

        //all good
        return true;
    };

    /**
     * Check to see if a session is authentic
     *
     * @method isAuthenticated
     * @param {Object} session
     */
    SecurityService.isAuthenticated = function(session) {
        if (typeof session !== 'object') {
            return false;
        }
        var reqs = {};
        reqs[SecurityService.AUTHENTICATED] = true;
        return SecurityService.isAuthorized(session, reqs);
    };

    /**
     * One way encrypt a string
     *
     * @method encrypt
     * @param {String} valStr
     * #return {String} Encrypted string
     */
    SecurityService.encrypt = function(valStr) {
        var whirlpool = crypto.createHash('whirlpool');
        whirlpool.update(valStr);
        return whirlpool.digest('hex');
    };

    /**
     * @static
     * @method generatePassword
     * @param {Integer} [length=8]
     */
    SecurityService.generatePassword = function(length) {

        //ensure a length
        if (!pb.validation.isInt(length, true, true) || length < 8) {
            length = 8;
        }

        var password = [];
        while(password.length < length) {
            password.push(PASSWORD_CHARS[parseInt(Math.random() * PASSWORD_CHARS.length)]);
        }
        return password.join('');
    };

    /**
     *
     * @static
     * @method getPrincipal
     * @param {Object} session
     * @return {Object} The authenticated user principal or NULL if not authenticated
     */
    SecurityService.getPrincipal = function(session) {
        if (util.isObject(session) && util.isObject(session.authentication)) {
            return session.authentication.user;
        }
        return null;
    };

    return SecurityService;
};