Skip to content

Commit

Permalink
Merge pull request #17611 from strapi/feature/rw-stage-rbac
Browse files Browse the repository at this point in the history
Feature: Stage permissions for review workflows
  • Loading branch information
gu-stav committed Sep 25, 2023
2 parents fdf22f1 + 9404aa6 commit 4d6918a
Show file tree
Hide file tree
Showing 64 changed files with 3,168 additions and 806 deletions.
255 changes: 255 additions & 0 deletions api-tests/core/admin/ee/review-workflows-stage-permissions.test.api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
'use strict';

const { mapAsync } = require('@strapi/utils');

const { createStrapiInstance } = require('api-tests/strapi');
const { createAuthRequest, createRequest } = require('api-tests/request');
const { createTestBuilder } = require('api-tests/builder');
const { describeOnCondition } = require('api-tests/utils');

const {
STAGE_MODEL_UID,
WORKFLOW_MODEL_UID,
ENTITY_STAGE_ATTRIBUTE,
} = require('../../../../packages/core/admin/ee/server/constants/workflows');
const { create } = require('lodash');

const edition = process.env.STRAPI_DISABLE_EE === 'true' ? 'CE' : 'EE';

const productUID = 'api::product.product';
const model = {
draftAndPublish: true,
pluginOptions: {},
singularName: 'product',
pluralName: 'products',
displayName: 'Product',
kind: 'collectionType',
attributes: {
name: {
type: 'string',
},
},
options: {
reviewWorkflows: true,
},
};

const baseWorkflow = {
contentTypes: [productUID],
stages: [{ name: 'Stage 1' }, { name: 'Stage 2' }],
};

const getStageTransitionPermissions = (roleIds) => {
return roleIds.map((roleId) => ({
action: 'admin::review-workflows.stage.transition',
role: roleId,
}));
};

describeOnCondition(edition === 'EE')('Review workflows', () => {
const builder = createTestBuilder();

let strapi;
let workflow;
let rq;
let roles;

const createWorkflow = async (data) => {
const name = `workflow-${Math.random().toString(36)}`;
const req = await rq.post('/admin/review-workflows/workflows?populate=*', {
body: { data: { name, ...data } },
});

const status = req.statusCode;
const error = req.body.error;
const workflow = req.body.data;

return { workflow, status, error };
};

const updateWorkflow = async (id, data) => {
const req = await rq.put(`/admin/review-workflows/workflows/${id}?populate=stages`, {
body: { data },
});

const status = req.statusCode;
const error = req.body.error;
const workflow = req.body.data;

return { workflow, status, error };
};

const deleteWorkflow = async (id) => {
const req = await rq.delete(`/admin/review-workflows/workflows/${id}`);

const status = req.statusCode;
const error = req.body.error;
const workflow = req.body.data;

return { workflow, status, error };
};

beforeAll(async () => {
await builder.addContentTypes([model]).build();

strapi = await createStrapiInstance();
rq = await createAuthRequest({ strapi });

workflow = await createWorkflow(baseWorkflow);

// Get default roles
const { body } = await rq.get('/admin/roles');
roles = body.data;
});

afterAll(async () => {
await strapi.destroy();
await builder.cleanup();
});

describe('Assign workflow permissions', () => {
// Create stage with permissions
test('Can assign new stage permissions', async () => {
const { workflow } = await createWorkflow({
...baseWorkflow,
stages: [
{
...baseWorkflow.stages[0],
permissions: getStageTransitionPermissions([roles[0].id, roles[1].id]),
},
baseWorkflow.stages[1],
],
});

expect(workflow.stages[0].permissions).toHaveLength(2);
});

// Can unassign a role
test('Can remove stage permissions', async () => {
// Create workflow with permissions to transition to role 0 and 1
const { workflow } = await createWorkflow({
...baseWorkflow,
stages: [
{
...baseWorkflow.stages[0],
permissions: getStageTransitionPermissions([roles[0].id, roles[1].id]),
},
baseWorkflow.stages[1],
],
});

// Update workflow to remove role 1
const { workflow: updatedWorkflow } = await updateWorkflow(workflow.id, {
stages: [
{
...workflow.stages[0],
permissions: getStageTransitionPermissions([roles[0].id]),
},
workflow.stages[1],
],
});

// Validate that permissions have been removed from database
const deletedPermission = await strapi.query('admin::permission').findOne({
where: {
id: workflow.stages[0].permissions[1].id,
},
});

expect(updatedWorkflow.stages[0].permissions).toHaveLength(1);
expect(deletedPermission).toBeNull();
});

test('Deleting stage removes permissions', async () => {
const { workflow } = await createWorkflow({
...baseWorkflow,
stages: [
{
...baseWorkflow.stages[0],
permissions: getStageTransitionPermissions([roles[0].id, roles[1].id]),
},
baseWorkflow.stages[1],
],
});

const { workflow: updatedWorkflow } = await updateWorkflow(workflow.id, {
...workflow,
stages: [workflow.stages[1]],
});

// Deleted stage permissions should be removed from database
const permissions = await strapi.query('admin::permission').findMany({
where: {
id: { $in: workflow.stages[0].permissions.map((p) => p.id) },
},
});

expect(permissions).toHaveLength(0);
});

test('Deleting workflow removes permissions', async () => {
const { workflow } = await createWorkflow({
...baseWorkflow,
stages: [
{
...baseWorkflow.stages[0],
permissions: getStageTransitionPermissions([roles[0].id, roles[1].id]),
},
],
});

await deleteWorkflow(workflow.id);

// Deleted workflow permissions should be removed from database
const permissions = await strapi.query('admin::permission').findMany({
where: {
id: { $in: workflow.stages[0].permissions.map((p) => p.id) },
},
});

expect(permissions).toHaveLength(0);
});

test('Fails when using invalid action', async () => {
const { status, error } = await createWorkflow({
...baseWorkflow,
stages: [
{
...baseWorkflow.stages[0],
permissions: [{ action: 'invalid-action', role: roles[0].id }],
},
],
});

expect(status).toBe(400);
expect(error.name).toBe('ValidationError');
});

// TODO
test.skip('Can send permissions as undefined to apply partial update', async () => {
// Creates workflow with permissions
const { workflow } = await createWorkflow({
...baseWorkflow,
stages: [
{
...baseWorkflow.stages[0],
permissions: [{ action: 'invalid-action', role: roles[0].id }],
},
],
});

const { workflow: updatedWorkflow } = await updateWorkflow(workflow.id, {
...workflow,
stages: [
{
...workflow.stages[0],
permissions: undefined,
},
],
});

// Permissions should be kept
expect(updatedWorkflow.stages[0].permissions).toHaveLength(1);
});
});
});

0 comments on commit 4d6918a

Please sign in to comment.