linagora/openpaas-esn

View on GitHub
backend/webserver/middleware/configuration.js

Summary

Maintainability
F
1 wk
Test Coverage
'use strict';

const q = require('q');
const { promisify } = require('util');
const composableMw = require('composable-middleware');
const platformadminsMW = require('./platformadmins');
const helperMW = require('./helper');
const domainMW = require('./domain');
const authorizationMW = require('./authorization');
const logger = require('../../core/logger');
const rights = require('../../core/esn-config/rights');
const validator = require('../../core/esn-config/validator');
const { SCOPE } = require('../../core/esn-config/constants');
const domainModule = require('../../core/domain');
const userModule = require('../../core/user');

const checkUserIsDomainAdministrator = promisify(domainModule.userIsDomainAdministrator);
const getDomainById = promisify(domainModule.load);
const getUserById = promisify(userModule.get);

module.exports = {
  qualifyScopeQueries,
  checkAuthorizedRole,
  checkReadPermission,
  checkWritePermission,
  ensureWellformedBody,
  validateWriteBody,
  qualifyTargetUser
};

function qualifyScopeQueries(req, res, next) {
  const scope = req.query.scope;

  if (scope === SCOPE.platform) {
    delete req.query.domain_id;
    delete req.query.user_id;
  } else if (scope === SCOPE.domain) {
    delete req.query.user_id;
  } else if (scope === SCOPE.user) {
    req.query.user_id = req.query.user_id || req.user.id;
    req.query.domain_id = req.user.preferredDomainId;
  } else {
    return res.status(400).json({
      error: {
        code: 400,
        message: 'Bad Request',
        details: 'scope must be either platform, domain or user'
      }
    });
  }

  next();
}

function checkAuthorizedRole(req, res, next) {
  const scope = req.query.scope;
  const middlewares = [];

  if (scope === SCOPE.platform) {
    middlewares.push(platformadminsMW.requirePlatformAdmin);
  } else if (scope === SCOPE.domain) {
    middlewares.push(helperMW.requireInQuery('domain_id', 'when scope is domain, domain_id is required'));
    middlewares.push(domainMW.loadFromDomainIdParameter);
    middlewares.push(authorizationMW.requiresDomainManager);
  }

  return composableMw(...middlewares)(req, res, next);
}

function checkReadPermission(req, res, next) {
  const scope = req.query.scope;
  const middlewares = [];

  if (scope === SCOPE.platform) {
    middlewares.push(canReadPlatformConfig);
  } else if (scope === SCOPE.domain) {
    middlewares.push(canReadAdminConfig);
  } else if (scope === SCOPE.user) {
    middlewares.push(canReadUserConfig);
  }

  return composableMw(...middlewares)(req, res, next);
}

function checkWritePermission(req, res, next) {
  const scope = req.query.scope;
  const middlewares = [];

  if (scope === SCOPE.platform) {
    middlewares.push(canWritePlatformConfig);
  } else if (scope === SCOPE.domain) {
    middlewares.push(canWriteAdminConfig);
  } else if (scope === SCOPE.user) {
    middlewares.push(canWriteUserConfig);
  }

  return composableMw(...middlewares)(req, res, next);
}

function ensureWellformedBody(req, res, next) {
  const modules = req.body;
  let message;

  modules.some(module => {
    if (!module) {
      message = 'one of modules in array is null';

      return true;
    }

    if (!module.name) {
      message = 'one of modules in array does not have name';

      return true;
    }
  });

  if (message) {
    return res.status(400).json({
      error: {
        code: 400,
        message: 'Bad Request',
        details: `body data is not well-formed: ${message}`
      }
    });
  }

  next();
}

function validateWriteBody(req, res, next) {
  const modules = req.body;

  const invalidModules = modules.map(module => {
    if (!Array.isArray(module.configurations)) {
      return module.name;
    }

    return module.configurations.some(configuration => !configuration.name) ? module.name : null;
  }).filter(Boolean);

  if (invalidModules.length > 0) {
    return res.status(400).json({
      error: {
        code: 400,
        message: 'Bad Request',
        details: `module ${invalidModules.join(', ')} must have "configurations" attribute as an array of {name, value}`
      }
    });
  }

  const validationPromises = modules.map(module =>
    module.configurations.map(configuration =>
      validator.validate(module.name, configuration.name, configuration.value).then(result => ({
        moduleName: module.name,
        configName: configuration.name,
        result
      }))
    )
  );

  q.all([].concat(...validationPromises)).then(validations => {
    const invalidValidations = validations.filter(validation => !validation.result.ok);

    if (invalidValidations.length > 0) {
      const details = invalidValidations.map(validation =>
        `${validation.moduleName}->${validation.configName}: ${validation.result.message}`
      ).join('; ');

      return res.status(400).json({
        error: {
          code: 400,
          message: 'Bad Request',
          details
        }
      });
    }

    next();
  }, err => {
    const details = 'Error while validating configurations';

    logger.error(details, err);

    return res.status(500).json({
      error: {
        code: 500,
        message: 'Server Error',
        details
      }
    });
  });
}

function canWriteUserConfig(req, res, next) {
  const modules = req.body;

  const hasUnwritableConfig = modules.some(module => {
    if (!module || !Array.isArray(module.configurations)) {
      return true;
    }

    return module.configurations.some(config => {
      if (!config) {
        return true;
      }

      return !rights.userCanWrite(module.name, config.name);
    });
  });

  if (hasUnwritableConfig) {
    return res.status(400).json({
      error: {
        code: 400,
        message: 'Bad Request',
        details: 'Configurations are not writable'
      }
    });
  }

  next();
}

function canReadUserConfig(req, res, next) {
  const modules = req.body;

  const hasUnreadableConfig = modules.some(module => {
    if (!module || !Array.isArray(module.keys)) {
      return true;
    }

    return module.keys.some(key => !rights.userCanRead(module.name, key));
  });

  if (hasUnreadableConfig) {
    return res.status(400).json({
      error: {
        code: 400,
        message: 'Bad Request',
        details: 'Configurations are not readable'
      }
    });
  }

  next();
}

function canWriteAdminConfig(req, res, next) {
  const modules = req.body;

  const hasUnwritableConfig = modules.some(module => {
    if (!module || !Array.isArray(module.configurations)) {
      return true;
    }

    return module.configurations.some(config => {
      if (!config) {
        return true;
      }

      return !rights.adminCanWrite(module.name, config.name);
    });
  });

  if (hasUnwritableConfig) {
    return res.status(400).json({
      error: {
        code: 400,
        message: 'Bad Request',
        details: 'Configurations are not writable'
      }
    });
  }

  next();
}

function canReadAdminConfig(req, res, next) {
  const modules = req.body;

  const hasUnreadableConfig = modules.some(module => {
    if (!module || !Array.isArray(module.keys)) {
      return true;
    }

    return module.keys.some(key => !rights.adminCanRead(module.name, key));
  });

  if (hasUnreadableConfig) {
    return res.status(400).json({
      error: {
        code: 400,
        message: 'Bad Request',
        details: 'Configurations are not readable'
      }
    });
  }

  next();
}

function canWritePlatformConfig(req, res, next) {
  const modules = req.body;

  const hasUnwritableConfig = modules.some(module => {
    if (!module || !Array.isArray(module.configurations)) {
      return true;
    }

    return module.configurations.some(config => {
      if (!config) {
        return true;
      }

      return !rights.padminCanWrite(module.name, config.name);
    });
  });

  if (hasUnwritableConfig) {
    return res.status(400).json({
      error: {
        code: 400,
        message: 'Bad Request',
        details: 'Configurations are not writable'
      }
    });
  }

  next();
}

function canReadPlatformConfig(req, res, next) {
  const modules = req.body;

  const hasUnreadableConfig = modules.some(module => {
    if (!module || !Array.isArray(module.keys)) {
      return true;
    }

    return module.keys.some(key => !rights.padminCanRead(module.name, key));
  });

  if (hasUnreadableConfig) {
    return res.status(400).json({
      error: {
        code: 400,
        message: 'Bad Request',
        details: 'Configurations are not readable'
      }
    });
  }

  next();
}

function qualifyTargetUser(req, res, next) {
  if (req.query.scope !== SCOPE.user) {
    return next();
  }

  if (String(req.query.user_id) === String(req.user.id)) {
    return next();
  }

  const domainId = req.query.domain_id;

  return getDomainById(domainId)
    .then(domain => checkUserIsDomainAdministrator(req.user, domain))
    .then(isDomainAdministrator => {
      if (!isDomainAdministrator) {
        return res.status(403).json({
          error: {
            code: 403,
            message: 'Forbidden',
            details: 'Domain administrator role is required to do this action'
          }
        });
      }

      return getUserById(req.query.user_id);
    })
    .then(user => {
      if (!user) {
        return res.status(404).json({
          error: {
            code: 404,
            message: 'Not Found',
            details: 'Target user not found'
          }
        });
      }

      if (!userModule.domain.isMemberOfDomain(user, domainId)) {
        return res.status(403).json({
          error: {
            code: 403,
            message: 'Forbidden',
            details: 'Target user must be a member of the domain'
          }
        });
      }
      next();
    })
    .catch(err => {
      logger.error('Error while checking target user', err);

      return res.status(500).json({
        error: {
          code: 500,
          message: 'Server Error',
          details: 'Error while checking target user'
        }
      });
    });
}