OpenVeo Core server

API Docs for: 7.0.0
Show:

File: app/server/controllers/AuthenticationController.js

'use strict';

/**
 * @module core-controllers
 */

var util = require('util');
var passport = require('passport');
var openVeoApi = require('@openveo/api');
var pathUtil = process.require('app/server/path.js');
var errors = process.require('app/server/httpErrors.js');
var storage = process.require('app/server/storage.js');
var Controller = openVeoApi.controllers.Controller;

/**
 * Retrieves, recursively, the permission corresponding to the couple url / http method.
 *
 * @example
 *     var permissions = [
 *       {
 *         label: 'Permissions group',
 *         permissions: [
 *           {
 *             id: 'perm-1',
 *             name: 'Name of the permission',
 *             description: 'Description of the permission',
 *             paths: [ 'get /publishVideo' ]
 *           }
 *         ]
 *       }
 *     ];
 *     getPermissionByUrl(permissions, '/publishVideo', 'GET'); // "perm-1"
 *     getPermissionByUrl(permissions, '/video', 'GET'); // null
 *
 * @method getPermissionByUrl
 * @private
 * @static
 * @param {Array} permissions The list of permissions
 * @param {String} url An url
 * @param {String} httpMethod The http method (POST, GET, PUT, DELETE)
 * @return {String} The permission id if found, null otherwise
 */
function getPermissionByUrl(permissions, url, httpMethod) {
  var permissionsList = [];
  for (var i = 0; i < permissions.length; i++) {

    // Single permission
    if (permissions[i].id) {
      if (permissions[i].paths) {
        for (var j = 0; j < permissions[i].paths.length; j++) {
          var path = permissions[i].paths[j];
          if (pathUtil.validate(httpMethod + ' ' + url, path))
            permissionsList.push(permissions[i].id);
        }
      }
    } else if (permissions[i].label) {

      // Group of permissions
      var permissionId = getPermissionByUrl(permissions[i].permissions, url, httpMethod);

      if (permissionId && permissionId.length > 0) {
        permissionsList = permissionsList.concat(permissionId);
      }
    }
  }
  if (permissionsList.length == 0)
    return null;
  else
    return permissionsList;
}

/**
 * Checks if asked page is the user profile.
 *
 * All users must have access to its profile.
 *
 * @method isUserProfileUrl
 * @private
 * @param {Object} request The express request object handled by the server
 * @param {Object} request.user The connected user
 * @param {String} request.user.id The connected user id
 * @param {Boolean} request.user.locked true if user is locked, false otherwise
 * @param {String} request.method Request's HTTP method
 * @param {String} request.path Request's path
 * @return {Boolean} true if the page is the user profile page, false otherwise
 */
function isUserProfileUrl(request) {
  var path = '/users/' + request.user.id;
  return !request.user.locked && ((request.path === path) && (request.method === 'POST'));
}

/**
 * Defines a controller to handlerequests relative to back end authentication.
 *
 * @class authenticationController
 * @extends Controller
 * @constructor
 */
function AuthenticationController() {
  AuthenticationController.super_.call(this);
}

module.exports = AuthenticationController;
util.inherits(AuthenticationController, Controller);

/**
 * Handles user authentication using internal providers (which do not require a redirection to a third party site).
 *
 * @method authenticateInternalAction
 * @static
 * @async
 * @param {Request} request ExpressJS HTTP Request
 * @param {Object} request.body Request's body
 * @param {String} request.body.login The login
 * @param {String} request.body.password The password
 * @param {Response} response ExpressJS HTTP Response
 * @param {Function} next Function to defer execution to the next registered middleware
 */
AuthenticationController.prototype.authenticateInternalAction = function(request, response, next) {
  try {
    request.body = openVeoApi.util.shallowValidateObject(request.body, {
      login: {type: 'string', required: true},
      password: {type: 'string', required: true}
    });
  } catch (error) {
    process.logger.error(error.message);
    return next(errors.AUTHENTICATE_INTERNAL_WRONG_PARAMETERS);
  }

  // Get all internal strategies
  var internalStrategies = [];
  Object.keys(passport._strategies).forEach(function(strategy) {
    if (passport._strategies[strategy].internal)
      internalStrategies.push(strategy);
  });

  passport.authenticate(internalStrategies, function(error, user) {

    // An error occurred while authenticating
    // Dispatch the error
    if (error) {
      process.logger.error(error.message, {error: error, method: 'authenticateInternalAction'});
      return next(errors.BACK_END_AUTHENTICATION_ERROR);
    }

    // No user was found for the given login / password
    // Send back a 401 Not Authorized
    if (!user)
      return next(errors.BACK_END_AUTHENTICATION_FAILED);

    // Establish a session, authenticate the request
    request.login(user, function(loginError) {
      if (loginError) {
        process.logger.error(loginError.message, {error: loginError, method: 'authenticateInternalAction'});
        return next(errors.BACK_END_AUTHENTICATION_ERROR);
      }

      return response.status(200).send(user);
    });

  })(request, response, next);
};

/**
 * Handles user authentication using external providers (which require a redirection on third party site).
 *
 * @method authenticateExternalAction
 * @static
 * @async
 * @param {Request} request ExpressJS HTTP Request
 * @param {Object} request.params Request's parameters
 * @param {String} request.params.type The authentication provider to use
 * @param {Response} response ExpressJS HTTP Response
 * @param {Function} next Function to defer execution to the next registered middleware
 */
AuthenticationController.prototype.authenticateExternalAction = function(request, response, next) {
  try {
    request.params = openVeoApi.util.shallowValidateObject(request.params, {
      type: {type: 'string', in: Object.values(openVeoApi.passport.STRATEGIES), required: true}
    });
  } catch (error) {
    process.logger.error(error.message);
    return next(errors.AUTHENTICATE_EXTERNAL_WRONG_PARAMETERS);
  }

  passport.authenticate(request.params.type, function(error, user) {

    // An error occurred while authenticating
    // Dispatch the error
    if (error) {
      process.logger.error(error.message, {error: error, method: 'authenticateExternalAction'});
      return next(errors.BACK_END_EXTERNAL_AUTHENTICATION_ERROR);
    }

    // No user was found for the given login / password
    if (!user)
      return next(errors.BACK_END_EXTERNAL_AUTHENTICATION_FAILED);

    // Establish a session, authenticate the request
    request.login(user, function(loginError) {
      if (loginError) {
        process.logger.error(loginError.message, {error: loginError, method: 'authenticateExternalAction'});
        return next(errors.BACK_END_EXTERNAL_AUTHENTICATION_ERROR);
      }

      return response.redirect('/be');
    });

  })(request, response, next);
};

/**
 * Logs out user.
 *
 * An HTTP code 200 is returned to the client with no content.
 *
 * @method logoutAction
 * @param {Request} request ExpressJS HTTP Request
 * @param {Response} response ExpressJS HTTP Response
 * @param {Function} next Function to defer execution to the next registered middleware
 */
AuthenticationController.prototype.logoutAction = function(request, response, next) {
  if (!request.isAuthenticated()) return next();

  // Retrieve strategy from loaded strategies
  var strategyPrototype = passport._strategy(request.user.origin);

  // Logout from passport
  request.logout();

  // Destroy session from session store
  request.session.destroy(function(error) {
    if (error)
      process.logger.error(error.message, {error: error, method: 'logoutAction'});

    var strategy = Object.create(strategyPrototype);

    // For internal strategies there is nothing more to do
    // For external strategies we have to logout the user from the third party provider
    if (!strategyPrototype.internal && strategy.logout)
      strategy.logout(request, response);
    else
      response.status(200).send();
  });
};

/**
 * Checks if current request is authenticated.
 *
 * If not send back an HTTP code 401 with appropriate page.
 * It just get to the next route action if permission is granted.
 *
 * @method restrictAction
 * @param {Request} request ExpressJS HTTP Request
 * @param {String} request.url Request's url
 * @param {String} request.method Request's method
 * @param {Object} request.user The connected user
 * @param {String} request.user.id The connected user id
 * @param {Array} request.user.permissions The connected user permissions
 * @param {Response} response ExpressJS HTTP Response
 * @param {Function} next Function to defer execution to the next registered middleware
 */
AuthenticationController.prototype.restrictAction = function(request, response, next) {
  var error = errors.BACK_END_UNAUTHORIZED;

  // User is authenticated
  if (request.isAuthenticated()) {
    error = errors.BACK_END_FORBIDDEN;

    // Get requested permission for this request
    var permissions = getPermissionByUrl(storage.getPermissions(), request.url, request.method);
    var superAdminId = storage.getConfiguration().superAdminId;

    // No particular permission requested : access granted by default
    // Also always grant access to super administrator
    if (!permissions || request.user.id === superAdminId || isUserProfileUrl(request))
      return next();

    // Checks if user has permission on this url
    // Iterates through user roles to find if requested permission
    // is part of his privileges
    if (request.user.permissions) {

      // Found permission : access granted
      for (var i = 0; i < permissions.length; i++)
        if (request.user.permissions.indexOf(permissions[i]) >= 0)
          return next();

    }

  }

  // Not authenticated
  return next(error);
};

/**
 * Gets the tree of groups / permissions and return it as a JSON object.
 *
 * @method getPermissionsAction
 * @param {Request} request ExpressJS HTTP Request
 * @param {Response} response ExpressJS HTTP Response
 * @param {Function} next Function to defer execution to the next registered middleware
 */
AuthenticationController.prototype.getPermissionsAction = function(request, response) {
  var permissions = storage.getPermissions();
  response.send({
    permissions: permissions
  });
};