RackHD/on-http

View on GitHub
lib/services/account-api-service.js

Summary

Maintainability
F
3 days
Test Coverage
// Copyright 2016, EMC, Inc.

'use strict';

var di = require('di'),
    nodeAcl = require( 'acl' );

module.exports = accountApiServiceFactory;
di.annotate(accountApiServiceFactory, new di.Provide('Http.Services.Api.Account'));
di.annotate(accountApiServiceFactory,
    new di.Inject(
        'Services.Waterline',
        'Errors',
        'Logger',
        '_',
        'Promise',
        'Constants',
        'Services.Configuration',
        'Assert'
    )
);
function accountApiServiceFactory(
    waterline,
    Errors,
    Logger,
    _,
    Promise,
    Constants,
    configuration,
    assert
) {
    var logger = Logger.initialize(accountApiServiceFactory);

    function AccountApiService() {
        this.acl = new nodeAcl(new nodeAcl.memoryBackend());  // jshint ignore:line
    }

    AccountApiService.prototype.start = function() {
        var acl = this.acl;
        var self = this;
        var endpoints = configuration.get('httpEndpoints', []);
        var authEnabled = _(endpoints).filter(function(endpoint) {
            return endpoint.authEnabled === true;
        }).compact().value();

        // Only initialize the service if an endpoint has enabled authn
        if(_.isEmpty(authEnabled)) {
            return;
        }

        return Promise.join( self.listUsers(), waterline.roles.find({}), function(users, roles) {
            var promises = _.map(users, function(user) {
                    return acl.addUserRoles(user.username, user.role);
                });
            var rolePromises = _.map(roles, function(roles) {
                    return acl.addRoleParents(roles.role, roles.privileges);
                });
            Array.prototype.push.apply(promises, rolePromises);
            return Promise.all(promises);
        }).then(function() {
            // Enforce a subset of hardcoded privileges:
            var operator = {
                role: 'Operator',
                privileges: ['Login', 'ConfigureComponents', 'ConfigureSelf']
            };
            var admin = {
                role: 'Administrator',
                privileges: ['Read', 'Write', 'Login', 'ConfigureManager',
                             'ConfigureUsers', 'ConfigureComponents',
                             'ConfigureSelf']
            };
            var readOnly = {
                role: 'ReadOnly',
                privileges: ['Read', 'Login', 'ConfigureSelf']
            };
            return Promise.all([
                self.createRole(readOnly),
                self.createRole(operator),
                self.createRole(admin)
            ]).catch(function () {
                //mask error here
            });
        });
    };

    AccountApiService.prototype.aclMethod = function(name) {
        var args = Array.prototype.slice.call(arguments, 1);
        return this.acl[name].apply(this.acl, args);
    };

    AccountApiService.prototype.listUsers = function() {
        var self = this;
        return Promise.map(waterline.localusers.find({}), function(user) {
            return Promise.props({
                id: user.id,
                username: user.username,
                role: user.role,
                privileges: self.aclMethod('_rolesParents', [user.role])
            });
        });
    };

    AccountApiService.prototype.getUserByName = function(username) {
        var self = this;
        return Promise.try(function() {
            assert.string(username);
            return waterline.localusers.findOne({username: username});
        })
        .then(function(user) {
            if(!user) {
                throw new Errors.NotFoundError();
            }
            return Promise.props({
                id: user.id,
                username: user.username,
                role: user.role,
                privileges: self.aclMethod('_rolesParents', [user.role])
            });
        });
    };

    AccountApiService.prototype.createUser = function(user) {
        var self = this;
        return Promise.try(function() {
            assert.string(user.username);
            return waterline.localusers.findOne({username: user.username});
        })
        .then(function(entry) {
            if(entry) {
                // TODO: Replace with HttpError after errors are refactored
                var err = new Errors.BaseError('the user already exists');
                err.status = 409;
                throw err;
            }
            return waterline.localusers.create({
                username: user.username,
                password: user.password,
                role: user.role
            });
        })
        .then(function(entry) {
            return self.aclMethod('addUserRoles', entry.username, entry.role);
        })
        .then(function() {
            return self.getUserByName(user.username);
        });
    };

    AccountApiService.prototype.modifyUserByName = function(name, info) {
        var self = this;
        return Promise.try(function() {
            assert.string(name);
            return waterline.localusers.findOne({username: name});
        })
        .then(function(entry) {
            if(!entry) {
                throw new Errors.NotFoundError();
            }
            if(info.role && info.role !== entry.role) {
                return [entry, self.aclMethod('removeUserRoles', entry.username, entry.role)];
            }
            return [ entry ];
        })
        .spread(function(entry) {
            return [entry, waterline.localusers.update({username: name}, info)];
        })
        .spread(function(entry) {
            if(info.role && info.role !== entry.role) {
                return [entry, self.aclMethod('addUserRoles', entry.username, info.role)];
            }
            return [entry];
        })
        .then(function() {
            return self.getUserByName(name);
        });
    };

    AccountApiService.prototype.removeUserByName = function(name) {
        var self = this;
        return Promise.try(function() {
            assert.string(name);
        })
        .then(function() {
            return waterline.localusers.findOne({username: name});
        })
        .then(function(entry) {
            if(!entry) {
                throw new Errors.NotFoundError();
            }
            return Promise.all([
                self.aclMethod('removeUserRoles', entry.username, entry.role),
                waterline.localusers.destroy({username: name})
            ]);
        })
        .then(function() {
            return {
                username: name
            };
        });
    };

    AccountApiService.prototype.createRole = function (newrole) {
        var self = this;
        var acl = this.acl;
        return Promise.try(function () {
            assert.string(newrole.role);
            return waterline.roles.create({
                role: newrole.role,
                privileges: newrole.privileges
            });
        })
        .then(function (entry) {
            return acl.addRoleParents(entry.role, entry.privileges);
        })
        .then(function () {
            return self.getRoleByName(newrole.role);
        })
        .catch(function(err) {
            if (err.message.match(/A record with that `role` already exists/)) {
                err = new Errors.BaseError('role already exists');
                err.status = 409;
            }
            throw err;
        });
    };

    AccountApiService.prototype.getRoleByName = function (name) {
        var self = this;
        return Promise.try(function () {
            assert.string(name);
            return waterline.roles.findOne({ role: name });
        })
        .then(function (roleFound) {
            if (!roleFound) {
                throw new Errors.NotFoundError();
            }
            return Promise.props({
                role: roleFound.role,
                privileges: self.aclMethod('_rolesParents', [roleFound.role])
            });
        });
    };

    AccountApiService.prototype.listRoles = function () {
        var self = this;
        return Promise.map(waterline.roles.find({}), function (role) {
            return Promise.props({
                role: role.role,
                privileges: self.aclMethod('_rolesParents', [role.role])
            });
        });
    };

    AccountApiService.prototype.removeRoleByName = function (name) {
        var acl = this.acl;
        return Promise.try(function () {
            assert.string(name);
        })
        .then(function () {
            return waterline.roles.findOne({ role: name });
        })
        .then(function (entry) {
            if (!entry) {
                throw new Errors.NotFoundError();
            }
            return Promise.all([
                acl.removeRoleParents(entry.role),
                waterline.roles.destroy({ role: name })
            ]);
        })
        .then(function () {
            return {
                role: name
            };
        });
    };

    AccountApiService.prototype.modifyRoleByRoleName = function (name, info) {
        var self = this;
        var acl = this.acl;
        return Promise.try(function () {
            assert.string(name);
            return waterline.roles.findOne({ role: name });
        })
        .then(function (entry) {
            if (!entry) {
                throw new Errors.NotFoundError();
            }
            return acl.removeRoleParents(entry.role);
        })
        .then(function () {
            return waterline.roles.update({ role: name }, info.privileges);
        })
        .then(function () {
            return acl.addRoleParents(name, info.privileges);
        })
        .then(function () {
            return self.getRoleByName(name);
        });
    };

    return new AccountApiService();
}