From 60660204acce6e9e501c33161236d6831381fee6 Mon Sep 17 00:00:00 2001 From: Ryan Hutchison Date: Tue, 21 Jul 2015 00:35:58 -0400 Subject: [PATCH] Admin module base & user admin implementation. update displayName implements #700 (client-side role security) on angular routes. --- .../client/config/articles.client.routes.js | 2 +- .../chat/client/config/chat.client.routes.js | 2 +- .../client/config/core-admin.client.menus.js | 12 +++ .../client/config/core-admin.client.routes.js | 16 ++++ modules/core/client/core.client.module.js | 2 + .../client/config/users-admin.client.menus.js | 11 +++ .../config/users-admin.client.routes.js | 37 +++++++++ .../client/config/users.client.routes.js | 2 +- .../admin/user-list.client.controller.js | 31 +++++++ .../admin/user.client.controller.js | 34 ++++++++ .../client/services/users.client.service.js | 16 +++- modules/users/client/users.client.module.js | 4 +- .../views/admin/user-edit.client.view.html | 35 ++++++++ .../views/admin/user-list.client.view.html | 20 +++++ .../client/views/admin/user.client.view.html | 51 ++++++++++++ .../controllers/admin.server.controller.js | 83 +++++++++++++++++++ .../server/policies/admin.server.policies.js | 49 +++++++++++ .../server/routes/admin.server.routes.js | 22 +++++ 18 files changed, 424 insertions(+), 5 deletions(-) create mode 100644 modules/core/client/config/core-admin.client.menus.js create mode 100644 modules/core/client/config/core-admin.client.routes.js create mode 100644 modules/users/client/config/users-admin.client.menus.js create mode 100644 modules/users/client/config/users-admin.client.routes.js create mode 100644 modules/users/client/controllers/admin/user-list.client.controller.js create mode 100644 modules/users/client/controllers/admin/user.client.controller.js create mode 100644 modules/users/client/views/admin/user-edit.client.view.html create mode 100644 modules/users/client/views/admin/user-list.client.view.html create mode 100644 modules/users/client/views/admin/user.client.view.html create mode 100644 modules/users/server/controllers/admin.server.controller.js create mode 100644 modules/users/server/policies/admin.server.policies.js create mode 100644 modules/users/server/routes/admin.server.routes.js diff --git a/modules/articles/client/config/articles.client.routes.js b/modules/articles/client/config/articles.client.routes.js index ab0337a695..2505bec742 100644 --- a/modules/articles/client/config/articles.client.routes.js +++ b/modules/articles/client/config/articles.client.routes.js @@ -10,7 +10,7 @@ angular.module('articles').config(['$stateProvider', url: '/articles', template: '', data: { - roles: ['user'] + roles: ['user', 'admin'] } }). state('articles.list', { diff --git a/modules/chat/client/config/chat.client.routes.js b/modules/chat/client/config/chat.client.routes.js index 490a11da88..becabe53e0 100644 --- a/modules/chat/client/config/chat.client.routes.js +++ b/modules/chat/client/config/chat.client.routes.js @@ -8,7 +8,7 @@ angular.module('chat').config(['$stateProvider', url: '/chat', templateUrl: 'modules/chat/views/chat.client.view.html', data: { - roles: ['user'] + roles: ['user', 'admin'] } }); } diff --git a/modules/core/client/config/core-admin.client.menus.js b/modules/core/client/config/core-admin.client.menus.js new file mode 100644 index 0000000000..a2afa83668 --- /dev/null +++ b/modules/core/client/config/core-admin.client.menus.js @@ -0,0 +1,12 @@ +'use strict'; + +angular.module('core.admin').run(['Menus', + function (Menus) { + Menus.addMenuItem('topbar', { + title: 'Admin', + state: 'admin', + type: 'dropdown', + roles: ['admin'] + }); + } +]); diff --git a/modules/core/client/config/core-admin.client.routes.js b/modules/core/client/config/core-admin.client.routes.js new file mode 100644 index 0000000000..1697246fa7 --- /dev/null +++ b/modules/core/client/config/core-admin.client.routes.js @@ -0,0 +1,16 @@ +'use strict'; + +// Setting up route +angular.module('core.admin.routes').config(['$stateProvider', + function ($stateProvider) { + $stateProvider + .state('admin', { + abstract: true, + url: '/admin', + template: '', + data: { + roles: ['admin'] + } + }); + } +]); diff --git a/modules/core/client/core.client.module.js b/modules/core/client/core.client.module.js index b2658634cb..5850d6aec7 100644 --- a/modules/core/client/core.client.module.js +++ b/modules/core/client/core.client.module.js @@ -2,3 +2,5 @@ // Use Applicaion configuration module to register a new module ApplicationConfiguration.registerModule('core'); +ApplicationConfiguration.registerModule('core.admin', ['core']); +ApplicationConfiguration.registerModule('core.admin.routes', ['ui.router']); diff --git a/modules/users/client/config/users-admin.client.menus.js b/modules/users/client/config/users-admin.client.menus.js new file mode 100644 index 0000000000..33f998ae8f --- /dev/null +++ b/modules/users/client/config/users-admin.client.menus.js @@ -0,0 +1,11 @@ +'use strict'; + +// Configuring the Articles module +angular.module('users.admin').run(['Menus', + function (Menus) { + Menus.addSubMenuItem('topbar', 'admin', { + title: 'Manage Users', + state: 'admin.users' + }); + } +]); diff --git a/modules/users/client/config/users-admin.client.routes.js b/modules/users/client/config/users-admin.client.routes.js new file mode 100644 index 0000000000..8cf34221d0 --- /dev/null +++ b/modules/users/client/config/users-admin.client.routes.js @@ -0,0 +1,37 @@ +'use strict'; + +// Setting up route +angular.module('users.admin.routes').config(['$stateProvider', + function ($stateProvider) { + $stateProvider + .state('admin.users', { + url: '/users', + templateUrl: 'modules/users/views/admin/user-list.client.view.html', + controller: 'UserListController' + }) + .state('admin.user', { + url: '/users/:userId', + templateUrl: 'modules/users/views/admin/user.client.view.html', + controller: 'UserController', + resolve: { + userResolve: ['$stateParams', 'Admin', function ($stateParams, Admin) { + return Admin.get({ + userId: $stateParams.userId + }); + }] + } + }) + .state('admin.user-edit', { + url: '/users/:userId/edit', + templateUrl: 'modules/users/views/admin/user-edit.client.view.html', + controller: 'UserController', + resolve: { + userResolve: ['$stateParams', 'Admin', function ($stateParams, Admin) { + return Admin.get({ + userId: $stateParams.userId + }); + }] + } + }); + } +]); diff --git a/modules/users/client/config/users.client.routes.js b/modules/users/client/config/users.client.routes.js index 1646d50717..c38632650a 100644 --- a/modules/users/client/config/users.client.routes.js +++ b/modules/users/client/config/users.client.routes.js @@ -10,7 +10,7 @@ angular.module('users').config(['$stateProvider', url: '/settings', templateUrl: 'modules/users/views/settings/settings.client.view.html', data: { - roles: ['user'] + roles: ['user', 'admin'] } }). state('settings.profile', { diff --git a/modules/users/client/controllers/admin/user-list.client.controller.js b/modules/users/client/controllers/admin/user-list.client.controller.js new file mode 100644 index 0000000000..a552dd0e47 --- /dev/null +++ b/modules/users/client/controllers/admin/user-list.client.controller.js @@ -0,0 +1,31 @@ +'use strict'; + +angular.module('users.admin').controller('UserListController', ['$scope', '$filter', 'Admin', + function ($scope, $filter, Admin) { + Admin.query(function (data) { + $scope.users = data; + $scope.buildPager(); + }); + + $scope.buildPager = function () { + $scope.pagedItems = []; + $scope.itemsPerPage = 15; + $scope.currentPage = 1; + $scope.figureOutItemsToDisplay(); + }; + + $scope.figureOutItemsToDisplay = function () { + $scope.filteredItems = $filter('filter')($scope.users, { + $: $scope.search + }); + $scope.filterLength = $scope.filteredItems.length; + var begin = (($scope.currentPage - 1) * $scope.itemsPerPage); + var end = begin + $scope.itemsPerPage; + $scope.pagedItems = $scope.filteredItems.slice(begin, end); + }; + + $scope.pageChanged = function () { + $scope.figureOutItemsToDisplay(); + }; + } +]); diff --git a/modules/users/client/controllers/admin/user.client.controller.js b/modules/users/client/controllers/admin/user.client.controller.js new file mode 100644 index 0000000000..754316ec20 --- /dev/null +++ b/modules/users/client/controllers/admin/user.client.controller.js @@ -0,0 +1,34 @@ +'use strict'; + +angular.module('users.admin').controller('UserController', ['$scope', '$state', 'Authentication', 'userResolve', + function ($scope, $state, Authentication, userResolve) { + $scope.authentication = Authentication; + $scope.user = userResolve; + + $scope.remove = function (user) { + if (confirm('Are you sure you want to delete this user?')) { + if (user) { + user.$remove(); + + $scope.users.splice($scope.users.indexOf(user), 1); + } else { + $scope.user.$remove(function () { + $state.go('admin.users'); + }); + } + } + }; + + $scope.update = function () { + var user = $scope.user; + + user.$update(function () { + $state.go('admin.user', { + userId: user._id + }); + }, function (errorResponse) { + $scope.error = errorResponse.data.message; + }); + }; + } +]); diff --git a/modules/users/client/services/users.client.service.js b/modules/users/client/services/users.client.service.js index f8a68eeac2..eb3abfeee3 100644 --- a/modules/users/client/services/users.client.service.js +++ b/modules/users/client/services/users.client.service.js @@ -2,7 +2,7 @@ // Users service used for communicating with the users REST endpoint angular.module('users').factory('Users', ['$resource', - function($resource) { + function ($resource) { return $resource('api/users', {}, { update: { method: 'PUT' @@ -10,3 +10,17 @@ angular.module('users').factory('Users', ['$resource', }); } ]); + + +//TODO this should be Users service +angular.module('users.admin').factory('Admin', ['$resource', + function ($resource) { + return $resource('api/users/:userId', { + userId: '@_id' + }, { + update: { + method: 'PUT' + } + }); + } +]); diff --git a/modules/users/client/users.client.module.js b/modules/users/client/users.client.module.js index 569aba8c16..8a95d388ca 100644 --- a/modules/users/client/users.client.module.js +++ b/modules/users/client/users.client.module.js @@ -1,4 +1,6 @@ 'use strict'; // Use Applicaion configuration module to register a new module -ApplicationConfiguration.registerModule('users'); +ApplicationConfiguration.registerModule('users', ['core']); +ApplicationConfiguration.registerModule('users.admin', ['core.admin']); +ApplicationConfiguration.registerModule('users.admin.routes', ['core.admin.routes']); diff --git a/modules/users/client/views/admin/user-edit.client.view.html b/modules/users/client/views/admin/user-edit.client.view.html new file mode 100644 index 0000000000..26e274b96a --- /dev/null +++ b/modules/users/client/views/admin/user-edit.client.view.html @@ -0,0 +1,35 @@ +
+ +
+
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+
+ +
+
+
+
+
diff --git a/modules/users/client/views/admin/user-list.client.view.html b/modules/users/client/views/admin/user-list.client.view.html new file mode 100644 index 0000000000..dd933302b5 --- /dev/null +++ b/modules/users/client/views/admin/user-list.client.view.html @@ -0,0 +1,20 @@ +
+ + + + +
diff --git a/modules/users/client/views/admin/user.client.view.html b/modules/users/client/views/admin/user.client.view.html new file mode 100644 index 0000000000..eef7cf4703 --- /dev/null +++ b/modules/users/client/views/admin/user.client.view.html @@ -0,0 +1,51 @@ +
+ + +
+
+
+
First Name
+
+
+
+
+
Last Name
+
+
+
+
+
Email
+
+
+
+
+
Provider
+
+
+
+
+
Created
+
+
+
+
+
Roles
+
+
+
+
+
diff --git a/modules/users/server/controllers/admin.server.controller.js b/modules/users/server/controllers/admin.server.controller.js new file mode 100644 index 0000000000..c2998f9e9a --- /dev/null +++ b/modules/users/server/controllers/admin.server.controller.js @@ -0,0 +1,83 @@ +'use strict'; + +/** + * Module dependencies. + */ +var path = require('path'), + mongoose = require('mongoose'), + User = mongoose.model('User'), + errorHandler = require(path.resolve('./modules/core/server/controllers/errors.server.controller')); + +/** + * Show the current user + */ +exports.read = function (req, res) { + res.json(req.model); +}; + +/** + * Update a User + */ +exports.update = function (req, res) { + var user = req.model; + + //For security purposes only merge these parameters + user.firstName = req.body.firstName; + user.lastName = req.body.lastName; + user.displayName = user.firstName + ' ' + user.lastName; + user.roles = req.body.roles; + + user.save(function (err) { + if (err) { + return res.status(400).send({ + message: errorHandler.getErrorMessage(err) + }); + } + + res.json(user); + }); +}; + +/** + * Delete a user + */ +exports.delete = function (req, res) { + var user = req.model; + + user.remove(function (err) { + if (err) { + return res.status(400).send({ + message: errorHandler.getErrorMessage(err) + }); + } + + res.json(user); + }); +}; + +/** + * List of Users + */ +exports.list = function (req, res) { + User.find({}, '-salt -password').sort('-created').populate('user', 'displayName').exec(function (err, users) { + if (err) { + return res.status(400).send({ + message: errorHandler.getErrorMessage(err) + }); + } + + res.json(users); + }); +}; + +/** + * User middleware + */ +exports.userByID = function (req, res, next, id) { + User.findById(id, '-salt -password').exec(function (err, user) { + if (err) return next(err); + if (!user) return next(new Error('Failed to load user ' + id)); + req.model = user; + next(); + }); +}; diff --git a/modules/users/server/policies/admin.server.policies.js b/modules/users/server/policies/admin.server.policies.js new file mode 100644 index 0000000000..b0860ec82b --- /dev/null +++ b/modules/users/server/policies/admin.server.policies.js @@ -0,0 +1,49 @@ +'use strict'; + +/** + * Module dependencies. + */ +var acl = require('acl'); + +// Using the memory backend +acl = new acl(new acl.memoryBackend()); + +/** + * Invoke Articles Permissions + */ +exports.invokeRolesPolicies = function () { + acl.allow([{ + roles: ['admin'], + allows: [{ + resources: '/api/users', + permissions: '*' + }, { + resources: '/api/users/:userId', + permissions: '*' + }] + }]); +}; + +/** + * Check If Admin Policy Allows + */ +exports.isAllowed = function (req, res, next) { + var roles = (req.user) ? req.user.roles : ['guest']; + + // Check for user roles + acl.areAnyRolesAllowed(roles, req.route.path, req.method.toLowerCase(), function (err, isAllowed) { + if (err) { + // An authorization error occurred. + return res.status(500).send('Unexpected authorization error'); + } else { + if (isAllowed) { + // Access granted! Invoke next middleware + return next(); + } else { + return res.status(403).json({ + message: 'User is not authorized' + }); + } + } + }); +}; diff --git a/modules/users/server/routes/admin.server.routes.js b/modules/users/server/routes/admin.server.routes.js new file mode 100644 index 0000000000..d2fd4f4630 --- /dev/null +++ b/modules/users/server/routes/admin.server.routes.js @@ -0,0 +1,22 @@ +'use strict'; + +/** + * Module dependencies. + */ +var adminPolicy = require('../policies/admin.server.policies'), + admin = require('../controllers/admin.server.controller'); + +module.exports = function (app) { + // Users collection routes + app.route('/api/users').all(adminPolicy.isAllowed) + .get(admin.list); + + // Single user routes + app.route('/api/users/:userId').all(adminPolicy.isAllowed) + .get(admin.read) + .put(admin.update) + .delete(admin.delete); + + // Finish by binding the user middleware + app.param('userId', admin.userByID); +};