From 4c0aee7425bf7beea4d904a934be72ab7d8f0d48 Mon Sep 17 00:00:00 2001
From: Markus Grigull <web@grigull.me>
Date: Fri, 21 Oct 2016 19:37:21 +0200
Subject: [PATCH] Add user roles with validation on routes

Removed adminLevel from users
---
 auth.js                    | 13 +++++++-----
 config.js                  |  2 +-
 models/user.js             |  2 +-
 roles.js                   | 42 ++++++++++++++++++++++++++++++++++++++
 routes/plots.js            | 10 ++++-----
 routes/projects.js         | 10 ++++-----
 routes/simulationModels.js | 10 ++++-----
 routes/simulations.js      | 10 ++++-----
 routes/simulators.js       | 10 ++++-----
 routes/users.js            | 13 ++++++------
 routes/visualizations.js   | 10 ++++-----
 server.js                  |  2 +-
 12 files changed, 90 insertions(+), 44 deletions(-)
 create mode 100644 roles.js

diff --git a/auth.js b/auth.js
index 50b6a3b..ff7666a 100644
--- a/auth.js
+++ b/auth.js
@@ -11,6 +11,7 @@
 var jwt = require('jsonwebtoken');
 
 var config = require('./config');
+var roles = require('./roles');
 
 module.exports = {
   validateToken: function(req, res, next) {
@@ -31,14 +32,16 @@ module.exports = {
     });
   },
 
-  validateAdminLevel: function(requiredLevel) {
+  validateRole: function(resource, action) {
     return function(req, res, next) {
-      // check admin level
-      var userLevel = req.decoded._doc.adminLevel;
-      if (userLevel >= requiredLevel) {
+      // get user role
+      var role = roles[req.decoded._doc.role];
+      if (role.resource[resource].indexOf(action) > -1) {
+        // item found in list
         next();
       } else {
-        return res.status(401).send({ success: false, message: 'Invalid authorization' });
+        // item not found
+        return res.status(403).send({ success: false, message: 'Action not permitted' });
       }
     }
   }
diff --git a/config.js b/config.js
index af5d65a..df76b99 100644
--- a/config.js
+++ b/config.js
@@ -9,7 +9,7 @@
 
 module.exports = {
   databaseName: 'VILLAS',
-  databaseURL: 'mongodb://mongo:27017/',
+  databaseURL: 'mongodb://localhost:27017/',
   port: 3000,
   secret: 'longsecretislong',
   admin: {
diff --git a/models/user.js b/models/user.js
index 153a4a0..f4abbbf 100644
--- a/models/user.js
+++ b/models/user.js
@@ -20,7 +20,7 @@ var Schema = mongoose.Schema;
 var userSchema = new Schema({
   username: { type: String, unique: true, required: true },
   password: { type: String, required: true },
-  adminLevel: { type: Number, default: 0 },
+  role: { type: String, required: true, default: 'user' },
   projects: [{ type: Schema.Types.ObjectId, ref: 'Project', default: [] }],
   mail: { type: String, default: "" },
   simulations: [{ type: Schema.Types.ObjectId, ref: 'Simulation', default: [] }]
diff --git a/roles.js b/roles.js
new file mode 100644
index 0000000..c7530be
--- /dev/null
+++ b/roles.js
@@ -0,0 +1,42 @@
+/**
+ * File: roles.js
+ * Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
+ * Date: 20.10.2016
+ * Copyright: 2016, Institute for Automation of Complex Power Systems, EONERC
+ *   This file is part of VILLASweb. All Rights Reserved. Proprietary and confidential.
+ *   Unauthorized copying of this file, via any medium is strictly prohibited.
+ **********************************************************************************/
+
+module.exports = {
+  admin: {
+    id: 'admin',
+    name: 'Admin',
+    description: '',
+    resource: {
+      user: [ 'create', 'read', 'update', 'delete' ],
+      simulator: [ 'create', 'read', 'update', 'delete' ],
+      simulation: [ 'create', 'read', 'update', 'delete' ],
+      simulationModel: [ 'create', 'read', 'update', 'delete' ],
+      project: [ 'create', 'read', 'update', 'delete' ],
+      visualization: [ 'create', 'read', 'update', 'delete' ]
+    }
+  },
+  user: {
+    id: 'user',
+    name: 'User',
+    description: '',
+    resource: {
+      project: [ 'create', 'read', 'update', 'delete' ],
+      visualization: [ 'create', 'read', 'update', 'delete' ]
+    }
+  },
+  guest: {
+    id: 'guest',
+    name: 'Guest',
+    description: '',
+    resource: {
+      project: [ 'read' ],
+      visualization: [ 'read' ]
+    }
+  }
+}
diff --git a/routes/plots.js b/routes/plots.js
index 5089c32..2d73504 100644
--- a/routes/plots.js
+++ b/routes/plots.js
@@ -20,7 +20,7 @@ var Visualization = require('../models/visualization')
 var router = express.Router();
 
 // all plot routes need authentication
-router.use('/plots', auth.validateToken);
+router.use('/plots', auth.validateRole('visualization', 'read'), auth.validateToken);
 
 // routes
 router.get('/plots', function(req, res) {
@@ -34,7 +34,7 @@ router.get('/plots', function(req, res) {
   });
 });
 
-router.route('/plots').post(function(req, res) {
+router.post('/plots', auth.validateRole('visualization', 'create'), function(req, res) {
   // create new plot
   var plot = new Plot(req.body.plot);
 
@@ -62,7 +62,7 @@ router.route('/plots').post(function(req, res) {
   });
 });
 
-router.route('/plots/:id').put(function(req, res) {
+router.put('/plots/:id', auth.validateRole('visualization', 'update'), function(req, res) {
   // get plot
   Plot.findOne({ _id: req.params.id }, function(err, plot) {
     if (err) {
@@ -85,7 +85,7 @@ router.route('/plots/:id').put(function(req, res) {
   });
 });
 
-router.route('/plots/:id').get(function(req, res) {
+router.get('/plots/:id', auth.validateRole('visualization', 'read'), function(req, res) {
   Plot.findOne({ _id: req.params.id }, function(err, plot) {
     if (err) {
       return res.send(err);
@@ -95,7 +95,7 @@ router.route('/plots/:id').get(function(req, res) {
   });
 });
 
-router.route('/plots/:id').delete(function(req, res) {
+router.delete('/plots/:id', auth.validateRole('visualization', 'delete'), function(req, res) {
   Plot.findOne({ _id: req.params.id }, function(err, plot) {
     if (err) {
       return res.send(err);
diff --git a/routes/projects.js b/routes/projects.js
index 0c2557a..ebceaa0 100644
--- a/routes/projects.js
+++ b/routes/projects.js
@@ -24,7 +24,7 @@ var router = express.Router();
 router.use('/projects', auth.validateToken);
 
 // routes
-router.get('/projects', function(req, res) {
+router.get('/projects', auth.validateRole('project', 'read'), function(req, res) {
   // get all projects
   Project.find(function(err, projects) {
     if (err) {
@@ -35,7 +35,7 @@ router.get('/projects', function(req, res) {
   });
 });
 
-router.post('/projects', function(req, res) {
+router.post('/projects', auth.validateRole('project', 'create'), function(req, res) {
   // create new project
   var project = new Project(req.body.project);
 
@@ -79,7 +79,7 @@ router.post('/projects', function(req, res) {
   });
 });
 
-router.put('/projects/:id', function(req, res) {
+router.put('/projects/:id', auth.validateRole('project', 'update'), function(req, res) {
   // get project
   Project.findOne({ _id: req.params.id }, function(err, project) {
     if (err) {
@@ -175,7 +175,7 @@ router.put('/projects/:id', function(req, res) {
   });
 });
 
-router.get('/projects/:id', function(req, res) {
+router.get('/projects/:id', auth.validateRole('project', 'read'), function(req, res) {
   Project.findOne({ _id: req.params.id }, function(err, project) {
     if (err) {
       return res.status(400).send(err);
@@ -185,7 +185,7 @@ router.get('/projects/:id', function(req, res) {
   });
 });
 
-router.delete('/projects/:id', function(req, res) {
+router.delete('/projects/:id', auth.validateRole('project', 'delete'), function(req, res) {
   Project.findOne({ _id: req.params.id }, function(err, project) {
     if (err) {
       return res.status(400).send(err);
diff --git a/routes/simulationModels.js b/routes/simulationModels.js
index ed13cbf..cdb00dc 100644
--- a/routes/simulationModels.js
+++ b/routes/simulationModels.js
@@ -23,7 +23,7 @@ var router = express.Router();
 router.use('/simulationModels', auth.validateToken);
 
 // routes
-router.get('/simulationModels', function(req, res) {
+router.get('/simulationModels', auth.validateRole('simulationModel', 'read'), function(req, res) {
   // get all models
   SimulationModel.find(function(err, models) {
     if (err) {
@@ -34,7 +34,7 @@ router.get('/simulationModels', function(req, res) {
   });
 });
 
-router.post('/simulationModels', function(req, res) {
+router.post('/simulationModels', auth.validateRole('simulationModel', 'create'), function(req, res) {
   // create new model
   var model = new SimulationModel(req.body.simulationModel);
 
@@ -62,7 +62,7 @@ router.post('/simulationModels', function(req, res) {
   });
 });
 
-router.put('/simulationModels/:id', function(req, res) {
+router.put('/simulationModels/:id', auth.validateRole('simulationModel', 'update'), function(req, res) {
   // get model
   SimulationModel.findOne({ _id: req.params.id }, function(err, model) {
     if (err) {
@@ -85,7 +85,7 @@ router.put('/simulationModels/:id', function(req, res) {
   });
 });
 
-router.get('/simulationModels/:id', function(req, res) {
+router.get('/simulationModels/:id', auth.validateRole('simulationModel', 'read'), function(req, res) {
   SimulationModel.findOne({ _id: req.params.id }, function(err, model) {
     if (err) {
       return res.status(400).send(err);
@@ -95,7 +95,7 @@ router.get('/simulationModels/:id', function(req, res) {
   });
 });
 
-router.delete('/simulationModels/:id', function(req, res) {
+router.delete('/simulationModels/:id', auth.validateRole('simulationModel', 'delete'), function(req, res) {
   SimulationModel.findOne({ _id: req.params.id }, function(err, model) {
     if (err) {
       return res.status(400).send(err);
diff --git a/routes/simulations.js b/routes/simulations.js
index 022652f..d688681 100644
--- a/routes/simulations.js
+++ b/routes/simulations.js
@@ -23,7 +23,7 @@ var router = express.Router();
 router.use('/simulations', auth.validateToken);
 
 // routes
-router.get('/simulations', function(req, res) {
+router.get('/simulations', auth.validateRole('simulation', 'read'), function(req, res) {
   // get all simulations
   Simulation.find(function(err, simulations) {
     if (err) {
@@ -34,7 +34,7 @@ router.get('/simulations', function(req, res) {
   });
 });
 
-router.post('/simulations', function(req, res) {
+router.post('/simulations', auth.validateRole('simulation', 'create'), function(req, res) {
   // create new simulation
   var simulation = new Simulation(req.body.simulation);
 
@@ -63,7 +63,7 @@ router.post('/simulations', function(req, res) {
   });
 });
 
-router.put('/simulations/:id', function(req, res) {
+router.put('/simulations/:id', auth.validateRole('simulation', 'update'), function(req, res) {
   // get simulation
   Simulation.findOne({ _id: req.params.id }, function(err, simulation) {
     if (err) {
@@ -123,7 +123,7 @@ router.put('/simulations/:id', function(req, res) {
   });
 });
 
-router.get('/simulations/:id', function(req, res) {
+router.get('/simulations/:id', auth.validateRole('simulation', 'read'), function(req, res) {
   Simulation.findOne({ _id: req.params.id }, function(err, simulation) {
     if (err) {
       return res.send(err);
@@ -133,7 +133,7 @@ router.get('/simulations/:id', function(req, res) {
   });
 });
 
-router.delete('/simulations/:id', function(req, res) {
+router.delete('/simulations/:id', auth.validateRole('simulation', 'delete'), function(req, res) {
   Simulation.findOne({ _id: req.params.id }, function(err, simulation) {
     if (err) {
       return res.status(400).send(err);
diff --git a/routes/simulators.js b/routes/simulators.js
index 4057285..3f5f36e 100644
--- a/routes/simulators.js
+++ b/routes/simulators.js
@@ -22,7 +22,7 @@ var router = express.Router();
 router.use('/simulators', auth.validateToken);
 
 // routes
-router.get('/simulators', function(req, res) {
+router.get('/simulators', auth.validateRole('simulator', 'read'), function(req, res) {
   // get all simulators
   Simulator.find(function(err, simulators) {
     if (err) {
@@ -33,7 +33,7 @@ router.get('/simulators', function(req, res) {
   });
 });
 
-router.post('/simulators', function(req, res) {
+router.post('/simulators', auth.validateRole('simulator', 'create'), function(req, res) {
   // create new simulator
   var simulator = new Simulator(req.body.simulator);
 
@@ -46,7 +46,7 @@ router.post('/simulators', function(req, res) {
   });
 });
 
-router.put('/simulators/:id', function(req, res) {
+router.put('/simulators/:id', auth.validateRole('simulator', 'update'), function(req, res) {
   // get simulator
   Simulator.findOne({ _id: req.params.id }, function(err, simulator) {
     if (err) {
@@ -69,7 +69,7 @@ router.put('/simulators/:id', function(req, res) {
   });
 });
 
-router.get('/simulators/:id', function(req, res) {
+router.get('/simulators/:id', auth.validateRole('simulator', 'read'), function(req, res) {
   Simulator.findOne({ _id: req.params.id }, function(err, simulator) {
     if (err) {
       return res.status(400).send(err);
@@ -79,7 +79,7 @@ router.get('/simulators/:id', function(req, res) {
   });
 });
 
-router.delete('/simulators/:id', function(req, res) {
+router.delete('/simulators/:id', auth.validateRole('simulator', 'delete'), function(req, res) {
   Simulator.findOne({ _id: req.params.id }, function(err, simulator) {
     if (err) {
       return res.status(400).send(err);
diff --git a/routes/users.js b/routes/users.js
index 2a2a015..df985b3 100644
--- a/routes/users.js
+++ b/routes/users.js
@@ -24,7 +24,7 @@ var router = express.Router();
 router.use('/users', auth.validateToken);
 
 // routes
-router.route('/users').get(auth.validateAdminLevel(1), function(req, res) {
+router.get('/users', auth.validateRole('user', 'read'), function(req, res) {
   // get all users
   User.find(function(err, users) {
     if (err) {
@@ -35,7 +35,7 @@ router.route('/users').get(auth.validateAdminLevel(1), function(req, res) {
   });
 });
 
-router.route('/users').post(auth.validateAdminLevel(1), function(req, res) {
+router.post('/users', auth.validateRole('user', 'create'), function(req, res) {
   // create new user
   var user = new User(req.body.user);
 
@@ -48,7 +48,7 @@ router.route('/users').post(auth.validateAdminLevel(1), function(req, res) {
   });
 });
 
-router.route('/users/:id').put(function(req, res) {
+router.put('/users/:id', auth.validateRole('user', 'update'), function(req, res) {
   // get user
   User.findOne({ _id: req.params.id }, function(err, user) {
     if (err) {
@@ -90,7 +90,8 @@ router.route('/users/:id').put(function(req, res) {
   });
 });
 
-router.route('/users/me').get(function(req, res) {
+// This route needs no role validation, since it just requests the current user
+router.get('/users/me', function(req, res) {
   // get the logged-in user ID
   var userId = req.decoded._doc._id;
 
@@ -103,7 +104,7 @@ router.route('/users/me').get(function(req, res) {
   });
 });
 
-router.route('/users/:id').get(auth.validateAdminLevel(1), function(req, res) {
+router.get('/users/:id', auth.validateRole('user', 'read'), function(req, res) {
   User.findOne({ _id: req.params.id }, function(err, user) {
     if (err) {
       return res.send(err);
@@ -113,7 +114,7 @@ router.route('/users/:id').get(auth.validateAdminLevel(1), function(req, res) {
   });
 });
 
-router.route('/users/:id').delete(auth.validateAdminLevel(1), function(req, res) {
+router.delete('/users/:id', auth.validateRole('user', 'delete'), function(req, res) {
   User.findOne({ _id: req.params.id }, function(err, user) {
     if (err) {
       return res.send(err);
diff --git a/routes/visualizations.js b/routes/visualizations.js
index 89aec59..4f5f1e0 100644
--- a/routes/visualizations.js
+++ b/routes/visualizations.js
@@ -23,7 +23,7 @@ var router = express.Router();
 router.use('/visualizations', auth.validateToken);
 
 // routes
-router.get('/visualizations', function(req, res) {
+router.get('/visualizations', auth.validateRole('visualization', 'read'), function(req, res) {
   // get all visualizations
   Visualization.find(function(err, visualizations) {
     if (err) {
@@ -34,7 +34,7 @@ router.get('/visualizations', function(req, res) {
   });
 });
 
-router.route('/visualizations').post(function(req, res) {
+router.post('/visualizations', auth.validateRole('visualization', 'create'), function(req, res) {
   // create new visualization
   var visualization = new Visualization(req.body.visualization);
 
@@ -62,7 +62,7 @@ router.route('/visualizations').post(function(req, res) {
   });
 });
 
-router.route('/visualizations/:id').put(function(req, res) {
+router.put('/visualizations/:id', auth.validateRole('visualization', 'update'), function(req, res) {
   // get visualization
   Visualization.findOne({ _id: req.params.id }, function(err, visualization) {
     if (err) {
@@ -85,7 +85,7 @@ router.route('/visualizations/:id').put(function(req, res) {
   });
 });
 
-router.route('/visualizations/:id').get(function(req, res) {
+router.get('/visualizations/:id', auth.validateRole('visualization', 'read'), function(req, res) {
   Visualization.findOne({ _id: req.params.id }, function(err, visualization) {
     if (err) {
       return res.send(err);
@@ -95,7 +95,7 @@ router.route('/visualizations/:id').get(function(req, res) {
   });
 });
 
-router.route('/visualizations/:id').delete(function(req, res) {
+router.delete('/visualizations/:id', auth.validateRole('visualization', 'delete'), function(req, res) {
   Visualization.findOne({ _id: req.params.id }, function(err, visualization) {
     if (err) {
       return res.send(err);
diff --git a/server.js b/server.js
index 009969c..2fd484a 100644
--- a/server.js
+++ b/server.js
@@ -85,7 +85,7 @@ if (config.admin) {
 
     if (!user) {
       // create new admin user
-      var newUser = User({ username: config.admin.username, password: config.admin.password, adminLevel: 1});
+      var newUser = User({ username: config.admin.username, password: config.admin.password, role: 'admin' });
       newUser.save(function(err) {
         if (err) {
           console.log(err);
-- 
GitLab