@strapi/admin 4.13.2 → 4.14.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/admin/src/components/NpsSurvey/index.js +5 -2
- package/admin/src/hooks/useAdminRoles/index.js +17 -7
- package/admin/src/hooks/useAdminUsers/useAdminUsers.js +16 -7
- package/admin/src/hooks/useContentTypes/useContentTypes.js +18 -7
- package/build/1227.ec336799.chunk.js +1 -0
- package/build/{3483.19381b40.chunk.js → 3483.f6b2439f.chunk.js} +1 -1
- package/build/4174.4587c7f6.chunk.js +1 -0
- package/build/6266.53be9ea3.chunk.js +124 -0
- package/build/7897.eac204a4.chunk.js +6 -0
- package/build/{Admin-authenticatedApp.5a6b7544.chunk.js → Admin-authenticatedApp.d200a4ee.chunk.js} +2 -2
- package/build/{admin-app.2a8615ab.chunk.js → admin-app.582877a3.chunk.js} +11 -11
- package/build/admin-edit-roles-page.0aa65505.chunk.js +267 -0
- package/build/admin-edit-users.9215912a.chunk.js +10 -0
- package/build/admin-roles-list.824a50de.chunk.js +22 -0
- package/build/admin-users.f6b3c643.chunk.js +11 -0
- package/build/audit-logs-settings-page.be2cb4dd.chunk.js +1 -0
- package/build/{content-manager.f448efdf.chunk.js → content-manager.06a2f7ec.chunk.js} +39 -39
- package/build/index.html +1 -1
- package/build/review-workflows-settings-create-view.604cffa0.chunk.js +1 -0
- package/build/review-workflows-settings-edit-view.73c57f07.chunk.js +1 -0
- package/build/review-workflows-settings-list-view.7e300ecb.chunk.js +56 -0
- package/build/{runtime~main.ec4717bd.js → runtime~main.9de029f4.js} +2 -2
- package/build/sso-settings-page.94373f78.chunk.js +1 -0
- package/ee/admin/content-manager/pages/EditView/InformationBox/components/StageSelect/StageSelect.js +65 -53
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/actions/index.js +50 -5
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/Stages/Stage/Stage.js +227 -19
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/WorkflowAttributes/WorkflowAttributes.js +8 -23
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/constants.js +6 -0
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/hooks/useReviewWorkflows.js +17 -7
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/hooks/useReviewWorkflowsStages.js +36 -0
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/CreateView/CreateView.js +68 -19
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/EditView/EditView.js +105 -35
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/reducer/index.js +68 -27
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/selectors.js +45 -0
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/utils/validateWorkflow.js +20 -0
- package/ee/server/config/admin-actions.js +6 -0
- package/ee/server/constants/workflows.js +13 -0
- package/ee/server/content-types/workflow-stage/index.js +6 -0
- package/ee/server/controllers/workflows/index.js +41 -16
- package/ee/server/controllers/workflows/stages/index.js +93 -6
- package/ee/server/routes/review-workflows.js +10 -9
- package/ee/server/services/index.js +1 -0
- package/ee/server/services/review-workflows/stage-permissions.js +60 -0
- package/ee/server/services/review-workflows/stages.js +83 -12
- package/ee/server/services/review-workflows/workflows/index.js +20 -7
- package/ee/server/validation/review-workflows.js +11 -0
- package/package.json +8 -8
- package/scripts/build.js +2 -3
- package/scripts/create-dev-plugins-file.js +2 -3
- package/server/content-types/Permission.js +6 -0
- package/server/domain/permission/index.js +11 -2
- package/server/services/role.js +12 -4
- package/server/validation/action-provider.js +1 -1
- package/server/validation/common-validators.js +92 -100
- package/server/validation/permission.js +0 -3
- package/utils/create-cache-dir.js +5 -102
- package/utils/plugins.js +217 -0
- package/webpack.config.js +2 -2
- package/build/1227.9f37e1dc.chunk.js +0 -1
- package/build/2237.b832ae6e.chunk.js +0 -114
- package/build/4174.f1f39e40.chunk.js +0 -1
- package/build/4724.aea5c8c1.chunk.js +0 -6
- package/build/admin-edit-roles-page.38a6c863.chunk.js +0 -267
- package/build/admin-edit-users.545fc882.chunk.js +0 -10
- package/build/admin-roles-list.1e2e814d.chunk.js +0 -22
- package/build/admin-users.b8ea5677.chunk.js +0 -11
- package/build/audit-logs-settings-page.96f9d608.chunk.js +0 -1
- package/build/review-workflows-settings-create-view.4a156a19.chunk.js +0 -1
- package/build/review-workflows-settings-edit-view.ce984d1f.chunk.js +0 -1
- package/build/review-workflows-settings-list-view.419b8deb.chunk.js +0 -56
- package/build/sso-settings-page.45153df5.chunk.js +0 -1
- package/utils/create-plugins-exclude-path.js +0 -20
- package/utils/get-plugins.js +0 -110
|
@@ -3,7 +3,11 @@
|
|
|
3
3
|
const { mapAsync } = require('@strapi/utils');
|
|
4
4
|
const { getService } = require('../../../utils');
|
|
5
5
|
const { validateUpdateStageOnEntity } = require('../../../validation/review-workflows');
|
|
6
|
-
const {
|
|
6
|
+
const {
|
|
7
|
+
STAGE_MODEL_UID,
|
|
8
|
+
ENTITY_STAGE_ATTRIBUTE,
|
|
9
|
+
STAGE_TRANSITION_UID,
|
|
10
|
+
} = require('../../../constants/workflows');
|
|
7
11
|
|
|
8
12
|
/**
|
|
9
13
|
*
|
|
@@ -75,18 +79,36 @@ module.exports = {
|
|
|
75
79
|
*/
|
|
76
80
|
async updateEntity(ctx) {
|
|
77
81
|
const stagesService = getService('stages');
|
|
82
|
+
const stagePermissions = getService('stage-permissions');
|
|
78
83
|
const workflowService = getService('workflows');
|
|
79
84
|
|
|
80
|
-
const { model_uid: modelUID, id
|
|
85
|
+
const { model_uid: modelUID, id } = ctx.params;
|
|
81
86
|
const { body } = ctx.request;
|
|
82
87
|
|
|
83
|
-
const entityId = Number(entityIdString);
|
|
84
|
-
|
|
85
88
|
const { sanitizeOutput } = strapi
|
|
86
89
|
.plugin('content-manager')
|
|
87
90
|
.service('permission-checker')
|
|
88
91
|
.create({ userAbility: ctx.state.userAbility, model: modelUID });
|
|
89
92
|
|
|
93
|
+
// Load entity
|
|
94
|
+
const entity = await strapi.entityService.findOne(modelUID, Number(id), {
|
|
95
|
+
populate: [ENTITY_STAGE_ATTRIBUTE],
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
if (!entity) {
|
|
99
|
+
ctx.throw(404, 'Entity not found');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Validate if entity stage can be updated
|
|
103
|
+
const canTransition = stagePermissions.can(
|
|
104
|
+
STAGE_TRANSITION_UID,
|
|
105
|
+
entity[ENTITY_STAGE_ATTRIBUTE]?.id
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
if (!canTransition) {
|
|
109
|
+
ctx.throw(403, 'Forbidden stage transition');
|
|
110
|
+
}
|
|
111
|
+
|
|
90
112
|
const { id: stageId } = await validateUpdateStageOnEntity(
|
|
91
113
|
{ id: Number(body?.data?.id) },
|
|
92
114
|
'You should pass an id to the body of the put request.'
|
|
@@ -95,8 +117,73 @@ module.exports = {
|
|
|
95
117
|
const workflow = await workflowService.assertContentTypeBelongsToWorkflow(modelUID);
|
|
96
118
|
workflowService.assertStageBelongsToWorkflow(stageId, workflow);
|
|
97
119
|
|
|
98
|
-
const
|
|
120
|
+
const updatedEntity = await stagesService.updateEntity({ id: entity.id, modelUID }, stageId);
|
|
99
121
|
|
|
100
|
-
ctx.body = { data: await sanitizeOutput(
|
|
122
|
+
ctx.body = { data: await sanitizeOutput(updatedEntity) };
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* List all the stages that are available for a user to transition an entity to.
|
|
127
|
+
* If the user has permission to change the current stage of the entity every other stage in the workflow is returned
|
|
128
|
+
* @async
|
|
129
|
+
* @param {*} ctx
|
|
130
|
+
* @param {string} ctx.params.model_uid - The model UID of the entity.
|
|
131
|
+
* @param {string} ctx.params.id - The ID of the entity.
|
|
132
|
+
* @throws {ApplicationError} If review workflows is not activated on the specified model UID.
|
|
133
|
+
*/
|
|
134
|
+
async listAvailableStages(ctx) {
|
|
135
|
+
const stagePermissions = getService('stage-permissions');
|
|
136
|
+
const workflowService = getService('workflows');
|
|
137
|
+
|
|
138
|
+
const { model_uid: modelUID, id } = ctx.params;
|
|
139
|
+
|
|
140
|
+
if (
|
|
141
|
+
strapi
|
|
142
|
+
.plugin('content-manager')
|
|
143
|
+
.service('permission-checker')
|
|
144
|
+
.create({ userAbility: ctx.state.userAbility, model: modelUID })
|
|
145
|
+
.cannot.read()
|
|
146
|
+
) {
|
|
147
|
+
return ctx.forbidden();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Load entity
|
|
151
|
+
const entity = await strapi.entityService.findOne(modelUID, Number(id), {
|
|
152
|
+
populate: [ENTITY_STAGE_ATTRIBUTE],
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
if (!entity) {
|
|
156
|
+
ctx.throw(404, 'Entity not found');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const entityStageId = entity[ENTITY_STAGE_ATTRIBUTE]?.id;
|
|
160
|
+
const canTransition = stagePermissions.can(STAGE_TRANSITION_UID, entityStageId);
|
|
161
|
+
|
|
162
|
+
const [workflowCount, { stages: workflowStages }] = await Promise.all([
|
|
163
|
+
workflowService.count(),
|
|
164
|
+
workflowService.getAssignedWorkflow(modelUID, {
|
|
165
|
+
populate: 'stages',
|
|
166
|
+
}),
|
|
167
|
+
]);
|
|
168
|
+
|
|
169
|
+
const meta = {
|
|
170
|
+
stageCount: workflowStages.length,
|
|
171
|
+
workflowCount,
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
if (!canTransition) {
|
|
175
|
+
ctx.body = {
|
|
176
|
+
data: [],
|
|
177
|
+
meta,
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const data = workflowStages.filter((stage) => stage.id !== entityStageId);
|
|
184
|
+
ctx.body = {
|
|
185
|
+
data,
|
|
186
|
+
meta,
|
|
187
|
+
};
|
|
101
188
|
},
|
|
102
189
|
};
|
|
@@ -131,15 +131,16 @@ module.exports = {
|
|
|
131
131
|
handler: 'stages.updateEntity',
|
|
132
132
|
config: {
|
|
133
133
|
middlewares: [enableFeatureMiddleware('review-workflows')],
|
|
134
|
-
policies: [
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
],
|
|
134
|
+
policies: ['admin::isAuthenticatedAdmin'],
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
method: 'GET',
|
|
139
|
+
path: '/content-manager/(collection|single)-types/:model_uid/:id/stages',
|
|
140
|
+
handler: 'stages.listAvailableStages',
|
|
141
|
+
config: {
|
|
142
|
+
middlewares: [enableFeatureMiddleware('review-workflows')],
|
|
143
|
+
policies: ['admin::isAuthenticatedAdmin'],
|
|
143
144
|
},
|
|
144
145
|
},
|
|
145
146
|
{
|
|
@@ -8,6 +8,7 @@ module.exports = {
|
|
|
8
8
|
'seat-enforcement': require('./seat-enforcement'),
|
|
9
9
|
workflows: require('./review-workflows/workflows'),
|
|
10
10
|
stages: require('./review-workflows/stages'),
|
|
11
|
+
'stage-permissions': require('./review-workflows/stage-permissions'),
|
|
11
12
|
assignees: require('./review-workflows/assignees'),
|
|
12
13
|
'review-workflows': require('./review-workflows/review-workflows'),
|
|
13
14
|
'review-workflows-validation': require('./review-workflows/validation'),
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { prop } = require('lodash/fp');
|
|
4
|
+
const {
|
|
5
|
+
mapAsync,
|
|
6
|
+
errors: { ApplicationError },
|
|
7
|
+
} = require('@strapi/utils');
|
|
8
|
+
const { getService } = require('../../utils');
|
|
9
|
+
const { STAGE_TRANSITION_UID } = require('../../constants/workflows');
|
|
10
|
+
|
|
11
|
+
const validActions = [STAGE_TRANSITION_UID];
|
|
12
|
+
|
|
13
|
+
module.exports = ({ strapi }) => {
|
|
14
|
+
const roleService = getService('role');
|
|
15
|
+
const permissionService = getService('permission');
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
async register(roleId, action, fromStage) {
|
|
19
|
+
if (!validActions.includes(action)) {
|
|
20
|
+
throw new ApplicationError(`Invalid action ${action}`);
|
|
21
|
+
}
|
|
22
|
+
const permissions = await roleService.addPermissions(roleId, [
|
|
23
|
+
{
|
|
24
|
+
action,
|
|
25
|
+
actionParameters: {
|
|
26
|
+
from: fromStage,
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
// TODO: Filter response
|
|
32
|
+
return permissions;
|
|
33
|
+
},
|
|
34
|
+
async registerMany(permissions) {
|
|
35
|
+
return mapAsync(permissions, this.register);
|
|
36
|
+
},
|
|
37
|
+
async unregister(permissions) {
|
|
38
|
+
const permissionIds = permissions.map(prop('id'));
|
|
39
|
+
await permissionService.deleteByIds(permissionIds);
|
|
40
|
+
},
|
|
41
|
+
can(action, fromStage) {
|
|
42
|
+
const requestState = strapi.requestContext.get()?.state;
|
|
43
|
+
|
|
44
|
+
if (!requestState) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Override permissions for super admin
|
|
49
|
+
const userRoles = requestState.user?.roles;
|
|
50
|
+
if (userRoles?.some((role) => role.code === 'strapi-super-admin')) {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return requestState.userAbility.can({
|
|
55
|
+
name: action,
|
|
56
|
+
params: { from: fromStage },
|
|
57
|
+
});
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
};
|
|
@@ -2,15 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
const {
|
|
4
4
|
mapAsync,
|
|
5
|
+
reduceAsync,
|
|
5
6
|
errors: { ApplicationError, ValidationError },
|
|
6
7
|
} = require('@strapi/utils');
|
|
7
|
-
const { map } = require('lodash/fp');
|
|
8
|
+
const { map, pick, isEqual } = require('lodash/fp');
|
|
8
9
|
|
|
9
10
|
const { STAGE_MODEL_UID, ENTITY_STAGE_ATTRIBUTE, ERRORS } = require('../../constants/workflows');
|
|
10
11
|
const { getService } = require('../../utils');
|
|
11
12
|
|
|
13
|
+
const sanitizedStageFields = ['id', 'name', 'workflow', 'color'];
|
|
14
|
+
const sanitizeStageFields = pick(sanitizedStageFields);
|
|
15
|
+
|
|
12
16
|
module.exports = ({ strapi }) => {
|
|
13
17
|
const metrics = getService('review-workflows-metrics', { strapi });
|
|
18
|
+
const stagePermissionsService = getService('stage-permissions', { strapi });
|
|
14
19
|
const workflowsValidationService = getService('review-workflows-validation', { strapi });
|
|
15
20
|
|
|
16
21
|
return {
|
|
@@ -32,20 +37,64 @@ module.exports = ({ strapi }) => {
|
|
|
32
37
|
async createMany(stagesList, { fields } = {}) {
|
|
33
38
|
const params = { select: fields ?? '*' };
|
|
34
39
|
|
|
40
|
+
// TODO: pick the fields from the stage
|
|
35
41
|
const stages = await Promise.all(
|
|
36
42
|
stagesList.map((stage) =>
|
|
37
|
-
strapi.entityService.create(STAGE_MODEL_UID, {
|
|
43
|
+
strapi.entityService.create(STAGE_MODEL_UID, {
|
|
44
|
+
data: sanitizeStageFields(stage),
|
|
45
|
+
...params,
|
|
46
|
+
})
|
|
38
47
|
)
|
|
39
48
|
);
|
|
40
49
|
|
|
50
|
+
// Create stage permissions
|
|
51
|
+
await reduceAsync(stagesList)(async (_, stage, idx) => {
|
|
52
|
+
// Ignore stages without permissions
|
|
53
|
+
if (!stage.permissions || stage.permissions.length === 0) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const stagePermissions = stage.permissions;
|
|
58
|
+
const stageId = stages[idx].id;
|
|
59
|
+
|
|
60
|
+
const permissions = await mapAsync(
|
|
61
|
+
stagePermissions,
|
|
62
|
+
// Register each stage permission
|
|
63
|
+
(permission) =>
|
|
64
|
+
stagePermissionsService.register(permission.role, permission.action, stageId)
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
// Update stage with the new permissions
|
|
68
|
+
await strapi.entityService.update(STAGE_MODEL_UID, stageId, {
|
|
69
|
+
data: {
|
|
70
|
+
permissions: permissions.flat().map((p) => p.id),
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
}, []);
|
|
74
|
+
|
|
41
75
|
metrics.sendDidCreateStage();
|
|
42
76
|
|
|
43
77
|
return stages;
|
|
44
78
|
},
|
|
45
79
|
|
|
46
|
-
async update(
|
|
80
|
+
async update(srcStage, destStage) {
|
|
81
|
+
let stagePermissions = srcStage?.permissions ?? [];
|
|
82
|
+
const stageId = destStage.id;
|
|
83
|
+
|
|
84
|
+
if (destStage.permissions) {
|
|
85
|
+
await this.deleteStagePermissions([srcStage]);
|
|
86
|
+
|
|
87
|
+
const permissions = await mapAsync(destStage.permissions, (permission) =>
|
|
88
|
+
stagePermissionsService.register(permission.role, permission.action, stageId)
|
|
89
|
+
);
|
|
90
|
+
stagePermissions = permissions.flat().map((p) => p.id);
|
|
91
|
+
}
|
|
92
|
+
|
|
47
93
|
const stage = await strapi.entityService.update(STAGE_MODEL_UID, stageId, {
|
|
48
|
-
data:
|
|
94
|
+
data: {
|
|
95
|
+
...destStage,
|
|
96
|
+
permissions: stagePermissions,
|
|
97
|
+
},
|
|
49
98
|
});
|
|
50
99
|
|
|
51
100
|
metrics.sendDidEditStage();
|
|
@@ -53,20 +102,31 @@ module.exports = ({ strapi }) => {
|
|
|
53
102
|
return stage;
|
|
54
103
|
},
|
|
55
104
|
|
|
56
|
-
async delete(
|
|
57
|
-
|
|
105
|
+
async delete(stage) {
|
|
106
|
+
// Unregister all permissions related to this stage id
|
|
107
|
+
await this.deleteStagePermissions([stage]);
|
|
108
|
+
|
|
109
|
+
const deletedStage = await strapi.entityService.delete(STAGE_MODEL_UID, stage.id);
|
|
58
110
|
|
|
59
111
|
metrics.sendDidDeleteStage();
|
|
60
112
|
|
|
61
|
-
return
|
|
113
|
+
return deletedStage;
|
|
62
114
|
},
|
|
63
115
|
|
|
64
|
-
async deleteMany(
|
|
116
|
+
async deleteMany(stages) {
|
|
117
|
+
await this.deleteStagePermissions(stages);
|
|
118
|
+
|
|
65
119
|
return strapi.entityService.deleteMany(STAGE_MODEL_UID, {
|
|
66
|
-
filters: { id: { $in:
|
|
120
|
+
filters: { id: { $in: stages.map((s) => s.id) } },
|
|
67
121
|
});
|
|
68
122
|
},
|
|
69
123
|
|
|
124
|
+
async deleteStagePermissions(stages) {
|
|
125
|
+
// TODO: Find another way to do this for when we use the "to" parameter.
|
|
126
|
+
const permissions = stages.map((s) => s.permissions || []).flat();
|
|
127
|
+
await stagePermissionsService.unregister(permissions || []);
|
|
128
|
+
},
|
|
129
|
+
|
|
70
130
|
count({ workflowId } = {}) {
|
|
71
131
|
const opts = {};
|
|
72
132
|
|
|
@@ -91,7 +151,11 @@ module.exports = ({ strapi }) => {
|
|
|
91
151
|
const createdStagesIds = map('id', createdStages);
|
|
92
152
|
|
|
93
153
|
// Update the workflow stages
|
|
94
|
-
await mapAsync(updated, (
|
|
154
|
+
await mapAsync(updated, (destStage) => {
|
|
155
|
+
const srcStage = srcStages.find((s) => s.id === destStage.id);
|
|
156
|
+
|
|
157
|
+
return this.update(srcStage, destStage);
|
|
158
|
+
});
|
|
95
159
|
|
|
96
160
|
// Delete the stages that are not in the new stages list
|
|
97
161
|
await mapAsync(deleted, async (stage) => {
|
|
@@ -114,7 +178,7 @@ module.exports = ({ strapi }) => {
|
|
|
114
178
|
});
|
|
115
179
|
});
|
|
116
180
|
|
|
117
|
-
return this.delete(stage
|
|
181
|
+
return this.delete(stage);
|
|
118
182
|
});
|
|
119
183
|
|
|
120
184
|
return destStages.map((stage) => ({ ...stage, id: stage.id ?? createdStagesIds.shift() }));
|
|
@@ -241,12 +305,19 @@ module.exports = ({ strapi }) => {
|
|
|
241
305
|
*/
|
|
242
306
|
function getDiffBetweenStages(sourceStages, comparisonStages) {
|
|
243
307
|
const result = comparisonStages.reduce(
|
|
308
|
+
// ...
|
|
309
|
+
|
|
244
310
|
(acc, stageToCompare) => {
|
|
245
311
|
const srcStage = sourceStages.find((stage) => stage.id === stageToCompare.id);
|
|
246
312
|
|
|
247
313
|
if (!srcStage) {
|
|
248
314
|
acc.created.push(stageToCompare);
|
|
249
|
-
} else if (
|
|
315
|
+
} else if (
|
|
316
|
+
!isEqual(
|
|
317
|
+
pick(['name', 'color', 'permissions'], srcStage),
|
|
318
|
+
pick(['name', 'color', 'permissions'], stageToCompare)
|
|
319
|
+
)
|
|
320
|
+
) {
|
|
250
321
|
acc.updated.push(stageToCompare);
|
|
251
322
|
}
|
|
252
323
|
return acc;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { set, isString, map, get } = require('lodash/fp');
|
|
4
4
|
const { ApplicationError } = require('@strapi/utils').errors;
|
|
5
|
-
const { WORKFLOW_MODEL_UID } = require('../../../constants/workflows');
|
|
5
|
+
const { WORKFLOW_MODEL_UID, WORKFLOW_POPULATE } = require('../../../constants/workflows');
|
|
6
6
|
const { getService } = require('../../../utils');
|
|
7
7
|
const { getWorkflowContentTypeFilter } = require('../../../utils/review-workflows');
|
|
8
8
|
const workflowsContentTypesFactory = require('./content-types');
|
|
@@ -17,6 +17,16 @@ const processFilters = ({ strapi }, filters = {}) => {
|
|
|
17
17
|
return processedFilters;
|
|
18
18
|
};
|
|
19
19
|
|
|
20
|
+
// TODO: How can we improve this? Maybe using traversePopulate?
|
|
21
|
+
const processPopulate = (populate) => {
|
|
22
|
+
// If it does not exist or it's not an object (like an array) return the default populate
|
|
23
|
+
if (!populate) {
|
|
24
|
+
return populate;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return WORKFLOW_POPULATE;
|
|
28
|
+
};
|
|
29
|
+
|
|
20
30
|
module.exports = ({ strapi }) => {
|
|
21
31
|
const workflowsContentTypes = workflowsContentTypesFactory({ strapi });
|
|
22
32
|
const workflowsValidationService = getService('review-workflows-validation', { strapi });
|
|
@@ -31,7 +41,9 @@ module.exports = ({ strapi }) => {
|
|
|
31
41
|
*/
|
|
32
42
|
async find(opts = {}) {
|
|
33
43
|
const filters = processFilters({ strapi }, opts.filters);
|
|
34
|
-
|
|
44
|
+
const populate = processPopulate(opts.populate);
|
|
45
|
+
|
|
46
|
+
return strapi.entityService.findMany(WORKFLOW_MODEL_UID, { ...opts, filters, populate });
|
|
35
47
|
},
|
|
36
48
|
|
|
37
49
|
/**
|
|
@@ -41,7 +53,8 @@ module.exports = ({ strapi }) => {
|
|
|
41
53
|
* @returns {Promise<object>} - Workflow object matching the requested ID.
|
|
42
54
|
*/
|
|
43
55
|
findById(id, opts) {
|
|
44
|
-
|
|
56
|
+
const populate = processPopulate(opts.populate);
|
|
57
|
+
return strapi.entityService.findOne(WORKFLOW_MODEL_UID, id, { ...opts, populate });
|
|
45
58
|
},
|
|
46
59
|
|
|
47
60
|
/**
|
|
@@ -51,7 +64,7 @@ module.exports = ({ strapi }) => {
|
|
|
51
64
|
* @throws {ValidationError} - If the workflow has no stages.
|
|
52
65
|
*/
|
|
53
66
|
async create(opts) {
|
|
54
|
-
let createOpts = { ...opts, populate:
|
|
67
|
+
let createOpts = { ...opts, populate: WORKFLOW_POPULATE };
|
|
55
68
|
|
|
56
69
|
workflowsValidationService.validateWorkflowStages(opts.data.stages);
|
|
57
70
|
await workflowsValidationService.validateWorkflowCount(1);
|
|
@@ -87,7 +100,7 @@ module.exports = ({ strapi }) => {
|
|
|
87
100
|
*/
|
|
88
101
|
async update(workflow, opts) {
|
|
89
102
|
const stageService = getService('stages', { strapi });
|
|
90
|
-
let updateOpts = { ...opts, populate: {
|
|
103
|
+
let updateOpts = { ...opts, populate: { ...WORKFLOW_POPULATE } };
|
|
91
104
|
let updatedStageIds;
|
|
92
105
|
|
|
93
106
|
await workflowsValidationService.validateWorkflowCount();
|
|
@@ -104,7 +117,7 @@ module.exports = ({ strapi }) => {
|
|
|
104
117
|
.replaceStages(workflow.stages, opts.data.stages, workflow.contentTypes)
|
|
105
118
|
.then((stages) => stages.map((stage) => stage.id));
|
|
106
119
|
|
|
107
|
-
updateOpts = set('data.stages', updatedStageIds,
|
|
120
|
+
updateOpts = set('data.stages', updatedStageIds, updateOpts);
|
|
108
121
|
}
|
|
109
122
|
|
|
110
123
|
// Update (un)assigned Content Types
|
|
@@ -141,7 +154,7 @@ module.exports = ({ strapi }) => {
|
|
|
141
154
|
|
|
142
155
|
return strapi.db.transaction(async () => {
|
|
143
156
|
// Delete stages
|
|
144
|
-
await stageService.deleteMany(workflow.stages
|
|
157
|
+
await stageService.deleteMany(workflow.stages);
|
|
145
158
|
|
|
146
159
|
// Unassign all content types, this will migrate the content types to null
|
|
147
160
|
await workflowsContentTypes.migrate({
|
|
@@ -4,11 +4,22 @@
|
|
|
4
4
|
|
|
5
5
|
const { yup, validateYupSchema } = require('@strapi/utils');
|
|
6
6
|
const { hasStageAttribute } = require('../utils/review-workflows');
|
|
7
|
+
const { STAGE_TRANSITION_UID } = require('../constants/workflows');
|
|
7
8
|
|
|
8
9
|
const stageObject = yup.object().shape({
|
|
9
10
|
id: yup.number().integer().min(1),
|
|
10
11
|
name: yup.string().max(255).required(),
|
|
11
12
|
color: yup.string().matches(/^#(?:[0-9a-fA-F]{3}){1,2}$/i), // hex color
|
|
13
|
+
permissions: yup.array().of(
|
|
14
|
+
yup.object().shape({
|
|
15
|
+
role: yup.number().integer().min(1).required(),
|
|
16
|
+
action: yup.string().oneOf([STAGE_TRANSITION_UID]).required(),
|
|
17
|
+
actionParameters: yup.object().shape({
|
|
18
|
+
from: yup.number().integer().min(1).required(),
|
|
19
|
+
to: yup.number().integer().min(1),
|
|
20
|
+
}),
|
|
21
|
+
})
|
|
22
|
+
),
|
|
12
23
|
});
|
|
13
24
|
|
|
14
25
|
const validateUpdateStageOnEntity = yup
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@strapi/admin",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.14.0-alpha.0",
|
|
4
4
|
"description": "Strapi Admin",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -42,14 +42,14 @@
|
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@casl/ability": "^5.4.3",
|
|
44
44
|
"@pmmmwh/react-refresh-webpack-plugin": "0.5.10",
|
|
45
|
-
"@strapi/data-transfer": "4.
|
|
45
|
+
"@strapi/data-transfer": "4.14.0-alpha.0",
|
|
46
46
|
"@strapi/design-system": "1.9.0",
|
|
47
|
-
"@strapi/helper-plugin": "4.
|
|
47
|
+
"@strapi/helper-plugin": "4.14.0-alpha.0",
|
|
48
48
|
"@strapi/icons": "1.9.0",
|
|
49
|
-
"@strapi/permissions": "4.
|
|
50
|
-
"@strapi/provider-audit-logs-local": "4.
|
|
51
|
-
"@strapi/typescript-utils": "4.
|
|
52
|
-
"@strapi/utils": "4.
|
|
49
|
+
"@strapi/permissions": "4.14.0-alpha.0",
|
|
50
|
+
"@strapi/provider-audit-logs-local": "4.14.0-alpha.0",
|
|
51
|
+
"@strapi/typescript-utils": "4.14.0-alpha.0",
|
|
52
|
+
"@strapi/utils": "4.14.0-alpha.0",
|
|
53
53
|
"axios": "1.5.0",
|
|
54
54
|
"bcryptjs": "2.4.3",
|
|
55
55
|
"browserslist": "^4.17.3",
|
|
@@ -154,5 +154,5 @@
|
|
|
154
154
|
}
|
|
155
155
|
}
|
|
156
156
|
},
|
|
157
|
-
"gitHead": "
|
|
157
|
+
"gitHead": "1c3c286e2481cd8cbbb50e0e0bb8fbd0b824d2e4"
|
|
158
158
|
}
|
package/scripts/build.js
CHANGED
|
@@ -7,8 +7,7 @@ const { isObject } = require('lodash');
|
|
|
7
7
|
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin');
|
|
8
8
|
|
|
9
9
|
const webpackConfig = require('../webpack.config');
|
|
10
|
-
const { getPlugins } = require('../utils/
|
|
11
|
-
const { createPluginsJs } = require('../utils/create-cache-dir');
|
|
10
|
+
const { getPlugins, createPluginFile } = require('../utils/plugins');
|
|
12
11
|
|
|
13
12
|
// Wrapper that outputs the webpack speed
|
|
14
13
|
const smp = new SpeedMeasurePlugin();
|
|
@@ -30,7 +29,7 @@ const buildAdmin = async () => {
|
|
|
30
29
|
'@strapi/plugin-users-permissions',
|
|
31
30
|
]);
|
|
32
31
|
|
|
33
|
-
await
|
|
32
|
+
await createPluginFile(plugins, path.join(__dirname, '..'));
|
|
34
33
|
|
|
35
34
|
const args = {
|
|
36
35
|
entry,
|
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { join } = require('path');
|
|
4
4
|
const fs = require('fs-extra');
|
|
5
|
-
const { getPlugins } = require('../utils/
|
|
6
|
-
const { createPluginsJs } = require('../utils/create-cache-dir');
|
|
5
|
+
const { getPlugins, createPluginFile } = require('../utils/plugins');
|
|
7
6
|
|
|
8
7
|
/**
|
|
9
8
|
* Write the plugins.js file or copy the plugins-dev.js file if it exists
|
|
@@ -20,7 +19,7 @@ const createFile = async () => {
|
|
|
20
19
|
|
|
21
20
|
const plugins = getPlugins();
|
|
22
21
|
|
|
23
|
-
return
|
|
22
|
+
return createPluginFile(plugins, join(__dirname, '..'));
|
|
24
23
|
};
|
|
25
24
|
|
|
26
25
|
createFile()
|
|
@@ -26,8 +26,16 @@ const {
|
|
|
26
26
|
* @property {string} subject - The subject on which the permission should applies
|
|
27
27
|
*/
|
|
28
28
|
|
|
29
|
-
const permissionFields = [
|
|
30
|
-
|
|
29
|
+
const permissionFields = [
|
|
30
|
+
'id',
|
|
31
|
+
'action',
|
|
32
|
+
'actionParameters',
|
|
33
|
+
'subject',
|
|
34
|
+
'properties',
|
|
35
|
+
'conditions',
|
|
36
|
+
'role',
|
|
37
|
+
];
|
|
38
|
+
const sanitizedPermissionFields = ['id', 'action', 'actionParameters', 'subject', 'properties', 'conditions'];
|
|
31
39
|
|
|
32
40
|
const sanitizePermissionFields = pick(sanitizedPermissionFields);
|
|
33
41
|
|
|
@@ -36,6 +44,7 @@ const sanitizePermissionFields = pick(sanitizedPermissionFields);
|
|
|
36
44
|
* @return {Permission}
|
|
37
45
|
*/
|
|
38
46
|
const getDefaultPermission = () => ({
|
|
47
|
+
actionParameters: {},
|
|
39
48
|
conditions: [],
|
|
40
49
|
properties: {},
|
|
41
50
|
subject: null,
|
package/server/services/role.js
CHANGED
|
@@ -24,7 +24,7 @@ const ACTIONS = {
|
|
|
24
24
|
|
|
25
25
|
const sanitizeRole = omit(['users', 'permissions']);
|
|
26
26
|
|
|
27
|
-
const COMPARABLE_FIELDS = ['conditions', 'properties', 'subject', 'action'];
|
|
27
|
+
const COMPARABLE_FIELDS = ['conditions', 'properties', 'subject', 'action', 'actionParameters'];
|
|
28
28
|
const pickComparableFields = pick(COMPARABLE_FIELDS);
|
|
29
29
|
|
|
30
30
|
const jsonClean = (data) => JSON.parse(JSON.stringify(data));
|
|
@@ -323,6 +323,13 @@ const displayWarningIfNoSuperAdmin = async () => {
|
|
|
323
323
|
const assignPermissions = async (roleId, permissions = []) => {
|
|
324
324
|
await validatePermissionsExist(permissions);
|
|
325
325
|
|
|
326
|
+
// Internal actions are not handled by the role service, so any permission
|
|
327
|
+
// with an internal action is filtered out
|
|
328
|
+
const internalActions = getService('permission')
|
|
329
|
+
.actionProvider.values()
|
|
330
|
+
.filter((action) => action.section === 'internal')
|
|
331
|
+
.map((action) => action.actionId);
|
|
332
|
+
|
|
326
333
|
const superAdmin = await getService('role').getSuperAdmin();
|
|
327
334
|
const isSuperAdmin = superAdmin && superAdmin.id === roleId;
|
|
328
335
|
const assignRole = set('role', roleId);
|
|
@@ -342,13 +349,13 @@ const assignPermissions = async (roleId, permissions = []) => {
|
|
|
342
349
|
arePermissionsEqual,
|
|
343
350
|
permissionsWithRole,
|
|
344
351
|
existingPermissions
|
|
345
|
-
);
|
|
352
|
+
).filter((permission) => !internalActions.includes(permission.action));
|
|
346
353
|
|
|
347
354
|
const permissionsToDelete = differenceWith(
|
|
348
355
|
arePermissionsEqual,
|
|
349
356
|
existingPermissions,
|
|
350
357
|
permissionsWithRole
|
|
351
|
-
);
|
|
358
|
+
).filter((permission) => !internalActions.includes(permission.action));
|
|
352
359
|
|
|
353
360
|
const permissionsToReturn = differenceBy('id', permissionsToDelete, existingPermissions);
|
|
354
361
|
|
|
@@ -374,7 +381,8 @@ const addPermissions = async (roleId, permissions) => {
|
|
|
374
381
|
|
|
375
382
|
const permissionsWithRole = permissions
|
|
376
383
|
.map(set('role', roleId))
|
|
377
|
-
.map(sanitizeConditions(conditionProvider))
|
|
384
|
+
.map(sanitizeConditions(conditionProvider))
|
|
385
|
+
.map(permissionDomain.create);
|
|
378
386
|
|
|
379
387
|
return createMany(permissionsWithRole);
|
|
380
388
|
};
|
|
@@ -17,7 +17,7 @@ const registerProviderActionSchema = yup
|
|
|
17
17
|
(v) => `${v.path}: The id can only contain lowercase letters, dots and hyphens.`
|
|
18
18
|
)
|
|
19
19
|
.required(),
|
|
20
|
-
section: yup.string().oneOf(['contentTypes', 'plugins', 'settings']).required(),
|
|
20
|
+
section: yup.string().oneOf(['contentTypes', 'plugins', 'settings', 'internal']).required(),
|
|
21
21
|
pluginName: yup.mixed().when('section', {
|
|
22
22
|
is: 'plugins',
|
|
23
23
|
then: validators.isAPluginName.required(),
|