mirror of
				https://github.com/strapi/strapi.git
				synced 2025-11-03 19:36:20 +00:00 
			
		
		
		
	* Add a domain layer for the permission, rework the engine handling of the permissions Signed-off-by: Convly <jean-sebastien.herbaux@epitech.eu> * Add permissions-fields-to-properties migration for the admin Signed-off-by: Convly <jean-sebastien.herbaux@epitech.eu> * Removes useless console.log Signed-off-by: Convly <jean-sebastien.herbaux@epitech.eu> * Remove debug logLevel from provider-login.test.e2e.js Signed-off-by: Convly <jean-sebastien.herbaux@epitech.eu> * Adds the new layout for the GET permissions, allow to subscribe to actionRegistered events, adds i18n handlers Signed-off-by: Convly <jean-sebastien.herbaux@epitech.eu> * Fix typo Signed-off-by: Convly <jean-sebastien.herbaux@epitech.eu> * Update permissions validators Signed-off-by: Convly <jean-sebastien.herbaux@epitech.eu> * Update unit tests Signed-off-by: Convly <jean-sebastien.herbaux@epitech.eu> * Update integrations test + fix some validation issues Signed-off-by: Convly <jean-sebastien.herbaux@epitech.eu> * Change plugins & settings section format for the permissions layout * only return locales property to localized subjects for the permission's layout * Do not send the locales property to the permission's layout when there is no locales created * Add the 'locales' property to publish & delete routes * Fix unwanted mutation of the sections builder states on multiple builds * Fix units tests with (new engine) * Fix admin-role e2e test - Add locales property to the update payload * fix e2e testsé * Update e2e snapshots * Fix unit test for i18n bootstrap * Add mocks for i18n/bootstrap test * Fix has-locale condition & updatePermission validator * Avoid mutation in migration, always authorize super admin for has-locales condition * Rework rbac domain objects, add a hook module and a provider factory * Remove old providers * Update the admin services & tests for the new rbac domain & providers * Fix tests, bootstrap functions & services following rbac domain rework * Update migration runner * PR comments Signed-off-by: Convly <jean-sebastien.herbaux@epitech.eu> * Remove useless console.log * Fix sanitizeCondition bug * Section builder rework * Add test for the section-builder section & add jsdoc for the permission domain * pr comments (without the migrations) * fix fields-to-properties migration * Add jsdoc for the sections-builder * Moves createBoundAbstractDomain from permission domain to the engine service * Remove debug logLevel for admin role test (e2e) * Fix core-store * Fix hooks & move business logic from i18n bootstrap to dedicated services * add route get-non-localized-fields * use write and read permission * refacto * add input validator * add route doc * handle ST Co-authored-by: Pierre Noël <petersg83@gmail.com> Co-authored-by: Alexandre BODIN <alexandrebodin@users.noreply.github.com>
		
			
				
	
	
		
			215 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			215 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
'use strict';
 | 
						|
 | 
						|
const { yup } = require('strapi-utils');
 | 
						|
const _ = require('lodash');
 | 
						|
const { isEmpty, has, isNil, isArray } = require('lodash/fp');
 | 
						|
const { getService } = require('../utils');
 | 
						|
const actionDomain = require('../domain/action');
 | 
						|
const {
 | 
						|
  checkFieldsAreCorrectlyNested,
 | 
						|
  checkFieldsDontHaveDuplicates,
 | 
						|
} = require('./common-functions');
 | 
						|
 | 
						|
const getActionFromProvider = actionId => {
 | 
						|
  return getService('permission').actionProvider.get(actionId);
 | 
						|
};
 | 
						|
 | 
						|
const email = yup
 | 
						|
  .string()
 | 
						|
  .email()
 | 
						|
  .lowercase();
 | 
						|
 | 
						|
const firstname = yup.string().min(1);
 | 
						|
 | 
						|
const lastname = yup.string().min(1);
 | 
						|
 | 
						|
const username = yup.string().min(1);
 | 
						|
 | 
						|
const password = yup
 | 
						|
  .string()
 | 
						|
  .min(8)
 | 
						|
  .matches(/[a-z]/, '${path} must contain at least one lowercase character')
 | 
						|
  .matches(/[A-Z]/, '${path} must contain at least one uppercase character')
 | 
						|
  .matches(/\d/, '${path} must contain at least one number');
 | 
						|
 | 
						|
const roles = yup.array(yup.strapiID()).min(1);
 | 
						|
 | 
						|
const isAPluginName = yup
 | 
						|
  .string()
 | 
						|
  .test('is-a-plugin-name', 'is not a plugin name', function(value) {
 | 
						|
    return [undefined, 'admin', ...Object.keys(strapi.plugins)].includes(value)
 | 
						|
      ? true
 | 
						|
      : this.createError({ path: this.path, message: `${this.path} is not an existing plugin` });
 | 
						|
  });
 | 
						|
 | 
						|
const arrayOfConditionNames = yup
 | 
						|
  .array()
 | 
						|
  .of(yup.string())
 | 
						|
  .test('is-an-array-of-conditions', 'is not a plugin name', function(value) {
 | 
						|
    const ids = strapi.admin.services.permission.conditionProvider.keys();
 | 
						|
    return _.isUndefined(value) || _.difference(value, ids).length === 0
 | 
						|
      ? true
 | 
						|
      : this.createError({ path: this.path, message: `contains conditions that don't exist` });
 | 
						|
  });
 | 
						|
 | 
						|
const permissionsAreEquals = (a, b) =>
 | 
						|
  a.action === b.action && (a.subject === b.subject || (_.isNil(a.subject) && _.isNil(b.subject)));
 | 
						|
 | 
						|
const checkNoDuplicatedPermissions = permissions =>
 | 
						|
  !Array.isArray(permissions) ||
 | 
						|
  permissions.every((permA, i) =>
 | 
						|
    permissions.slice(i + 1).every(permB => !permissionsAreEquals(permA, permB))
 | 
						|
  );
 | 
						|
 | 
						|
const checkNilFields = action =>
 | 
						|
  function(fields) {
 | 
						|
    // If the parent has no action field, then we ignore this test
 | 
						|
    if (isNil(action)) {
 | 
						|
      return true;
 | 
						|
    }
 | 
						|
 | 
						|
    return actionDomain.appliesToProperty('fields', action) || isNil(fields);
 | 
						|
  };
 | 
						|
 | 
						|
const fieldsPropertyValidation = action =>
 | 
						|
  yup
 | 
						|
    .array()
 | 
						|
    .of(yup.string())
 | 
						|
    .nullable()
 | 
						|
    .test(
 | 
						|
      'field-nested',
 | 
						|
      'Fields format are incorrect (bad nesting).',
 | 
						|
      checkFieldsAreCorrectlyNested
 | 
						|
    )
 | 
						|
    .test(
 | 
						|
      'field-nested',
 | 
						|
      'Fields format are incorrect (duplicates).',
 | 
						|
      checkFieldsDontHaveDuplicates
 | 
						|
    )
 | 
						|
    .test(
 | 
						|
      'fields-restriction',
 | 
						|
      'The permission at ${path} must have fields set to null or undefined',
 | 
						|
      checkNilFields(action)
 | 
						|
    );
 | 
						|
 | 
						|
const updatePermissions = yup
 | 
						|
  .object()
 | 
						|
  .shape({
 | 
						|
    permissions: yup
 | 
						|
      .array()
 | 
						|
      .requiredAllowEmpty()
 | 
						|
      .of(
 | 
						|
        yup
 | 
						|
          .object()
 | 
						|
          .shape({
 | 
						|
            action: yup
 | 
						|
              .string()
 | 
						|
              .required()
 | 
						|
              .test('action-validity', 'action is not an existing permission action', function(
 | 
						|
                actionId
 | 
						|
              ) {
 | 
						|
                // If the action field is Nil, ignore the test and let the required check handle the error
 | 
						|
                if (isNil(actionId)) {
 | 
						|
                  return true;
 | 
						|
                }
 | 
						|
 | 
						|
                return !!getActionFromProvider(actionId);
 | 
						|
              }),
 | 
						|
            subject: yup
 | 
						|
              .string()
 | 
						|
              .nullable()
 | 
						|
              .test('subject-validity', 'Invalid subject submitted', function(subject) {
 | 
						|
                const action = getActionFromProvider(this.options.parent.action);
 | 
						|
 | 
						|
                if (!action) {
 | 
						|
                  return true;
 | 
						|
                }
 | 
						|
 | 
						|
                if (isNil(action.subjects)) {
 | 
						|
                  return isNil(subject);
 | 
						|
                }
 | 
						|
 | 
						|
                if (isArray(action.subjects)) {
 | 
						|
                  return action.subjects.includes(subject);
 | 
						|
                }
 | 
						|
 | 
						|
                return false;
 | 
						|
              }),
 | 
						|
            properties: yup
 | 
						|
              .object()
 | 
						|
              .test('properties-structure', 'Invalid property set at ${path}', function(
 | 
						|
                properties
 | 
						|
              ) {
 | 
						|
                const action = getActionFromProvider(this.options.parent.action);
 | 
						|
                const hasNoProperties = isEmpty(properties) || isNil(properties);
 | 
						|
 | 
						|
                if (!has('options.applyToProperties', action)) {
 | 
						|
                  return hasNoProperties;
 | 
						|
                }
 | 
						|
 | 
						|
                if (hasNoProperties) {
 | 
						|
                  return true;
 | 
						|
                }
 | 
						|
 | 
						|
                const { applyToProperties } = action.options;
 | 
						|
 | 
						|
                if (!isArray(applyToProperties)) {
 | 
						|
                  return false;
 | 
						|
                }
 | 
						|
 | 
						|
                return Object.keys(properties).every(property =>
 | 
						|
                  applyToProperties.includes(property)
 | 
						|
                );
 | 
						|
              })
 | 
						|
              .test('fields-property', 'Invalid fields property at ${path}', async function(
 | 
						|
                properties = {}
 | 
						|
              ) {
 | 
						|
                const action = getActionFromProvider(this.options.parent.action);
 | 
						|
 | 
						|
                if (!action || !properties) {
 | 
						|
                  return true;
 | 
						|
                }
 | 
						|
 | 
						|
                if (!actionDomain.appliesToProperty('fields', action)) {
 | 
						|
                  return true;
 | 
						|
                }
 | 
						|
 | 
						|
                try {
 | 
						|
                  await fieldsPropertyValidation(action).validate(properties.fields, {
 | 
						|
                    strict: true,
 | 
						|
                    abortEarly: false,
 | 
						|
                  });
 | 
						|
                  return true;
 | 
						|
                } catch (e) {
 | 
						|
                  // Propagate fieldsPropertyValidation error with updated path
 | 
						|
                  throw this.createError({
 | 
						|
                    message: e.message,
 | 
						|
                    path: `${this.path}.fields`,
 | 
						|
                  });
 | 
						|
                }
 | 
						|
              }),
 | 
						|
            conditions: yup.array().of(yup.string()),
 | 
						|
          })
 | 
						|
          .noUnknown()
 | 
						|
      )
 | 
						|
      .test(
 | 
						|
        'duplicated-permissions',
 | 
						|
        'Some permissions are duplicated (same action and subject)',
 | 
						|
        checkNoDuplicatedPermissions
 | 
						|
      ),
 | 
						|
  })
 | 
						|
  .required()
 | 
						|
  .noUnknown();
 | 
						|
 | 
						|
module.exports = {
 | 
						|
  email,
 | 
						|
  firstname,
 | 
						|
  lastname,
 | 
						|
  username,
 | 
						|
  password,
 | 
						|
  roles,
 | 
						|
  isAPluginName,
 | 
						|
  arrayOfConditionNames,
 | 
						|
  updatePermissions,
 | 
						|
};
 |