@strapi/admin 4.4.0-rc.0 → 4.5.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/StrapiApp.js +12 -4
- package/admin/src/components/Providers/index.js +10 -14
- package/admin/src/content-manager/components/CollectionTypeFormWrapper/index.js +11 -1
- package/admin/src/content-manager/components/DynamicTable/CellContent/RelationMultiple/index.js +11 -7
- package/admin/src/content-manager/components/DynamicTable/CellContent/index.js +6 -5
- package/admin/src/content-manager/components/DynamicTable/TableRows/index.js +5 -0
- package/admin/src/content-manager/components/DynamicTable/index.js +1 -1
- package/admin/src/content-manager/components/EditViewDataManagerProvider/index.js +23 -17
- package/admin/src/content-manager/components/EditViewDataManagerProvider/reducer.js +123 -24
- package/admin/src/content-manager/components/EditViewDataManagerProvider/utils/cleanData.js +17 -1
- package/admin/src/content-manager/components/FieldTypeIcon/index.js +1 -31
- package/admin/src/content-manager/components/Inputs/index.js +22 -36
- package/admin/src/content-manager/components/RelationInput/RelationInput.js +364 -0
- package/admin/src/content-manager/components/{SelectWrapper → RelationInput/components}/Option.js +15 -25
- package/admin/src/content-manager/components/RelationInput/components/Relation.js +48 -0
- package/admin/src/content-manager/components/RelationInput/components/RelationItem.js +52 -0
- package/admin/src/content-manager/components/RelationInput/components/RelationList.js +52 -0
- package/admin/src/content-manager/components/RelationInput/constants.js +1 -0
- package/admin/src/content-manager/components/RelationInput/index.js +1 -0
- package/admin/src/content-manager/components/RelationInputDataManager/RelationInputDataManager.js +250 -0
- package/admin/src/content-manager/components/RelationInputDataManager/constants.js +8 -0
- package/admin/src/content-manager/components/RelationInputDataManager/index.js +1 -0
- package/admin/src/content-manager/components/{SelectWrapper → RelationInputDataManager}/utils/connect.js +0 -1
- package/admin/src/content-manager/components/RelationInputDataManager/utils/getRelationLink.js +5 -0
- package/admin/src/content-manager/components/{SelectWrapper → RelationInputDataManager}/utils/index.js +1 -0
- package/admin/src/content-manager/components/RelationInputDataManager/utils/normalizeRelations.js +58 -0
- package/admin/src/content-manager/components/{SelectWrapper → RelationInputDataManager}/utils/select.js +31 -1
- package/admin/src/content-manager/components/SingleTypeFormWrapper/index.js +19 -2
- package/admin/src/content-manager/hooks/useFetchContentTypeLayout/utils/formatLayouts.js +7 -69
- package/admin/src/content-manager/hooks/useRelation/index.js +1 -0
- package/admin/src/content-manager/hooks/useRelation/useRelation.js +73 -0
- package/admin/src/content-manager/pages/EditSettingsView/components/DisplayedFields.js +4 -4
- package/admin/src/content-manager/pages/EditSettingsView/components/FormModal.js +2 -7
- package/admin/src/content-manager/pages/EditSettingsView/index.js +22 -51
- package/admin/src/content-manager/pages/EditSettingsView/reducer.js +0 -25
- package/admin/src/content-manager/pages/EditView/Header/index.js +3 -90
- package/admin/src/content-manager/pages/EditView/Header/utils/index.js +0 -1
- package/admin/src/content-manager/pages/EditView/Header/utils/select.js +0 -2
- package/admin/src/content-manager/pages/EditView/index.js +93 -155
- package/admin/src/content-manager/pages/ListView/FieldPicker/index.js +0 -1
- package/admin/src/content-manager/pages/ListView/index.js +0 -1
- package/admin/src/content-manager/pages/ListViewLayoutManager/index.js +0 -1
- package/admin/src/content-manager/utils/formatLayoutToApi.js +1 -3
- package/admin/src/core/apis/index.js +0 -1
- package/admin/src/hooks/index.js +0 -1
- package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/index.js +197 -215
- package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/utils/schema.js +1 -2
- package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/DeleteButton/index.js +0 -1
- package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/UpdateButton/index.js +36 -3
- package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/index.js +11 -13
- package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/index.js +2 -3
- package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/utils/tableHeaders.js +8 -8
- package/admin/src/pages/SettingsPage/pages/ApiTokens/ProtectedEditView/index.js +1 -1
- package/admin/src/permissions/defaultPermissions.js +6 -2
- package/admin/src/translations/ar.json +0 -1
- package/admin/src/translations/ca.json +0 -9
- package/admin/src/translations/cs.json +0 -2
- package/admin/src/translations/de.json +0 -11
- package/admin/src/translations/dk.json +0 -11
- package/admin/src/translations/en.json +0 -28
- package/admin/src/translations/es.json +0 -11
- package/admin/src/translations/fr.json +0 -11
- package/admin/src/translations/gu.json +0 -11
- package/admin/src/translations/hi.json +0 -11
- package/admin/src/translations/hu.json +0 -10
- package/admin/src/translations/id.json +0 -10
- package/admin/src/translations/it.json +0 -10
- package/admin/src/translations/ja.json +0 -11
- package/admin/src/translations/ko.json +0 -11
- package/admin/src/translations/ml.json +0 -11
- package/admin/src/translations/ms.json +0 -2
- package/admin/src/translations/nl.json +0 -11
- package/admin/src/translations/pl.json +0 -11
- package/admin/src/translations/pt-BR.json +0 -11
- package/admin/src/translations/pt.json +0 -1
- package/admin/src/translations/ru.json +0 -10
- package/admin/src/translations/sa.json +0 -11
- package/admin/src/translations/sk.json +0 -10
- package/admin/src/translations/sv.json +0 -9
- package/admin/src/translations/th.json +0 -2
- package/admin/src/translations/tr.json +0 -1
- package/admin/src/translations/uk.json +0 -2
- package/admin/src/translations/vi.json +0 -1
- package/admin/src/translations/zh-Hans.json +0 -12
- package/admin/src/translations/zh.json +0 -11
- package/build/7098.40dcd7bf.chunk.js +1 -0
- package/build/8851.e4ac62f2.chunk.js +158 -0
- package/build/{8773.c06c24c0.chunk.js → 9311.7cc03f29.chunk.js} +291 -108
- package/build/{Admin-authenticatedApp.9dec5230.chunk.js → Admin-authenticatedApp.e39f36c9.chunk.js} +3 -3
- package/build/{Admin_homePage.6d5e3236.chunk.js → Admin_homePage.118926e0.chunk.js} +1 -1
- package/build/{Admin_profilePage.da32abbc.chunk.js → Admin_profilePage.9d50ac44.chunk.js} +1 -1
- package/build/{Admin_settingsPage.98e2a62b.chunk.js → Admin_settingsPage.98a711e5.chunk.js} +16 -16
- package/build/admin-app.4f7618a9.chunk.js +112 -0
- package/build/admin-edit-roles-page.554ba3fa.chunk.js +1 -0
- package/build/api-tokens-create-page.4c262d6e.chunk.js +1 -0
- package/build/api-tokens-edit-page.10a9d368.chunk.js +1 -0
- package/build/api-tokens-list-page.442c9f3c.chunk.js +15 -0
- package/build/{ar-json.d4cb26d9.chunk.js → ar-json.3489463d.chunk.js} +1 -1
- package/build/{ca-json.d16c1d28.chunk.js → ca-json.a16899ae.chunk.js} +1 -1
- package/build/content-manager.7d57c9d1.chunk.js +1200 -0
- package/build/content-type-builder-list-view.8cc534e0.chunk.js +194 -0
- package/build/content-type-builder-translation-en-json.201bfb78.chunk.js +1 -0
- package/build/content-type-builder.684df7a4.chunk.js +142 -0
- package/build/{cs-json.c8f28ba8.chunk.js → cs-json.ce49da5c.chunk.js} +1 -1
- package/build/{de-json.a9b514dc.chunk.js → de-json.aa6026b3.chunk.js} +1 -1
- package/build/{dk-json.09e8d145.chunk.js → dk-json.fac2bcfb.chunk.js} +1 -1
- package/build/en-json.0c69c7d7.chunk.js +1 -0
- package/build/{es-json.3a9c7c09.chunk.js → es-json.d672e181.chunk.js} +1 -1
- package/build/{fr-json.4ed1fc2c.chunk.js → fr-json.71a16175.chunk.js} +1 -1
- package/build/{gu-json.d8311297.chunk.js → gu-json.ca345cd1.chunk.js} +1 -1
- package/build/{hi-json.0edb8d29.chunk.js → hi-json.50c7e6d4.chunk.js} +1 -1
- package/build/{hu-json.7855529a.chunk.js → hu-json.e0521dcc.chunk.js} +1 -1
- package/build/{id-json.df9618f2.chunk.js → id-json.4b1ff8d6.chunk.js} +1 -1
- package/build/index.html +1 -1
- package/build/{it-json.a21bf078.chunk.js → it-json.86bac220.chunk.js} +1 -1
- package/build/{ja-json.7b0d9067.chunk.js → ja-json.4e44e36b.chunk.js} +1 -1
- package/build/{ko-json.983c1f8f.chunk.js → ko-json.1003756e.chunk.js} +1 -1
- package/build/main.b47db1a3.js +9337 -0
- package/build/{ml-json.8dd021c8.chunk.js → ml-json.c7774425.chunk.js} +1 -1
- package/build/{ms-json.836ed013.chunk.js → ms-json.ed51e902.chunk.js} +1 -1
- package/build/{nl-json.29d2eb37.chunk.js → nl-json.f58ea235.chunk.js} +1 -1
- package/build/{pl-json.1f04f00c.chunk.js → pl-json.fed96aba.chunk.js} +1 -1
- package/build/{pt-BR-json.b4bc8efe.chunk.js → pt-BR-json.073799ab.chunk.js} +1 -1
- package/build/{pt-json.c23020ab.chunk.js → pt-json.3161ca22.chunk.js} +1 -1
- package/build/{ru-json.7ab40ccf.chunk.js → ru-json.7ad2cbbf.chunk.js} +1 -1
- package/build/{runtime~main.4204f341.js → runtime~main.feeac6d3.js} +1 -1
- package/build/{sa-json.c5a9f4ea.chunk.js → sa-json.f0f704f0.chunk.js} +1 -1
- package/build/{sk-json.e4c24c4e.chunk.js → sk-json.a848961b.chunk.js} +1 -1
- package/build/sso-settings-page.445184e0.chunk.js +1 -0
- package/build/{sv-json.c3f471ae.chunk.js → sv-json.b038acbe.chunk.js} +1 -1
- package/build/{th-json.a59ffb32.chunk.js → th-json.72e8de3d.chunk.js} +1 -1
- package/build/{tr-json.276e59fe.chunk.js → tr-json.9c44ea0c.chunk.js} +1 -1
- package/build/{uk-json.5b5b9c27.chunk.js → uk-json.c4cd2e24.chunk.js} +1 -1
- package/build/{vi-json.bf3424be.chunk.js → vi-json.f7890025.chunk.js} +1 -1
- package/build/{webhook-edit-page.9e46fc3f.chunk.js → webhook-edit-page.d2ea3351.chunk.js} +1 -1
- package/build/{zh-Hans-json.9c99f8d4.chunk.js → zh-Hans-json.03d2bda1.chunk.js} +1 -1
- package/build/{zh-json.451a0271.chunk.js → zh-json.3d0cc664.chunk.js} +1 -1
- package/package.json +7 -8
- package/server/bootstrap.js +1 -19
- package/server/config/admin-actions.js +0 -20
- package/server/content-types/api-token.js +1 -25
- package/server/content-types/index.js +0 -1
- package/server/controllers/api-token.js +1 -24
- package/server/controllers/index.js +0 -1
- package/server/routes/api-tokens.js +0 -11
- package/server/routes/index.js +0 -2
- package/server/services/api-token.js +29 -310
- package/server/services/constants.js +0 -10
- package/server/services/permission/engine-hooks.js +82 -0
- package/server/services/permission/engine.js +226 -36
- package/server/services/permission.js +1 -4
- package/server/strategies/admin.js +1 -7
- package/server/strategies/api-token.js +11 -71
- package/server/validation/api-tokens.js +2 -12
- package/admin/src/content-manager/components/SelectMany/ListItem.js +0 -102
- package/admin/src/content-manager/components/SelectMany/index.js +0 -148
- package/admin/src/content-manager/components/SelectOne/SingleValue.js +0 -67
- package/admin/src/content-manager/components/SelectOne/index.js +0 -97
- package/admin/src/content-manager/components/SelectWrapper/Label.js +0 -60
- package/admin/src/content-manager/components/SelectWrapper/index.js +0 -356
- package/admin/src/content-manager/pages/EditSettingsView/components/RelationalFieldButton.js +0 -135
- package/admin/src/content-manager/pages/EditSettingsView/components/RelationalFields.js +0 -103
- package/admin/src/content-manager/pages/EditView/Header/utils/getDraftRelations.js +0 -62
- package/admin/src/contexts/ApiTokenPermissions/index.js +0 -24
- package/admin/src/core/apis/CustomFields.js +0 -80
- package/admin/src/hooks/useRegenerate/index.js +0 -34
- package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/ActionBoundRoutes/index.js +0 -56
- package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/BoundRoute/getMethodColor.js +0 -41
- package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/BoundRoute/index.js +0 -72
- package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/CollapsableContentType/CheckBoxWrapper.js +0 -30
- package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/CollapsableContentType/index.js +0 -150
- package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/ContenTypesSection/index.js +0 -37
- package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/FormApiTokenContainer/index.js +0 -254
- package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/FormBody/index.js +0 -77
- package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/FormHead/index.js +0 -85
- package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/Permissions/index.js +0 -40
- package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/Regenerate/index.js +0 -68
- package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/init.js +0 -13
- package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/reducer.js +0 -72
- package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/utils/getDateOfExpiration.js +0 -16
- package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/utils/index.js +0 -5
- package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/utils/transformPermissionsData.js +0 -36
- package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/DefaultButton/index.js +0 -63
- package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/ReadButton/index.js +0 -19
- package/build/1669.d1b29c28.chunk.js +0 -1
- package/build/4318.7d167b58.chunk.js +0 -30
- package/build/524.40377968.chunk.js +0 -644
- package/build/7379.d246dd38.chunk.js +0 -1
- package/build/admin-app.a61d5c2e.chunk.js +0 -112
- package/build/admin-edit-roles-page.4dd6bcb9.chunk.js +0 -1
- package/build/api-tokens-create-page.93dd0689.chunk.js +0 -1
- package/build/api-tokens-edit-page.b0adac81.chunk.js +0 -1
- package/build/api-tokens-list-page.bb36535f.chunk.js +0 -16
- package/build/content-manager.feb0d540.chunk.js +0 -1178
- package/build/content-type-builder-list-view.5b3cd768.chunk.js +0 -194
- package/build/content-type-builder-translation-en-json.f985c9c4.chunk.js +0 -1
- package/build/content-type-builder.a684b2e8.chunk.js +0 -145
- package/build/en-json.a9918c93.chunk.js +0 -1
- package/build/main.e4065f58.js +0 -9337
- package/build/sso-settings-page.9ceb0140.chunk.js +0 -1
- package/server/content-types/api-token-permission.js +0 -36
- package/server/controllers/content-api.js +0 -15
- package/server/routes/content-api.js +0 -20
|
@@ -1,36 +1,107 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const {
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
const {
|
|
4
|
+
curry,
|
|
5
|
+
map,
|
|
6
|
+
filter,
|
|
7
|
+
propEq,
|
|
8
|
+
isFunction,
|
|
9
|
+
isBoolean,
|
|
10
|
+
isArray,
|
|
11
|
+
isNil,
|
|
12
|
+
isEmpty,
|
|
13
|
+
isObject,
|
|
14
|
+
prop,
|
|
15
|
+
merge,
|
|
16
|
+
pick,
|
|
17
|
+
difference,
|
|
18
|
+
cloneDeep,
|
|
19
|
+
} = require('lodash/fp');
|
|
20
|
+
const { AbilityBuilder, Ability } = require('@casl/ability');
|
|
21
|
+
const sift = require('sift');
|
|
6
22
|
const permissionDomain = require('../../domain/permission/index');
|
|
7
23
|
const { getService } = require('../../utils');
|
|
24
|
+
const {
|
|
25
|
+
createEngineHooks,
|
|
26
|
+
createWillEvaluateContext,
|
|
27
|
+
createWillRegisterContext,
|
|
28
|
+
} = require('./engine-hooks');
|
|
29
|
+
|
|
30
|
+
const allowedOperations = [
|
|
31
|
+
'$or',
|
|
32
|
+
'$and',
|
|
33
|
+
'$eq',
|
|
34
|
+
'$ne',
|
|
35
|
+
'$in',
|
|
36
|
+
'$nin',
|
|
37
|
+
'$lt',
|
|
38
|
+
'$lte',
|
|
39
|
+
'$gt',
|
|
40
|
+
'$gte',
|
|
41
|
+
'$exists',
|
|
42
|
+
'$elemMatch',
|
|
43
|
+
];
|
|
44
|
+
const operations = pick(allowedOperations, sift);
|
|
45
|
+
|
|
46
|
+
const conditionsMatcher = (conditions) => {
|
|
47
|
+
return sift.createQueryTester(conditions, { operations });
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
module.exports = (conditionProvider) => {
|
|
51
|
+
const state = {
|
|
52
|
+
hooks: createEngineHooks(),
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
hooks: state.hooks,
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Generate an ability based on the given user (using associated roles & permissions)
|
|
60
|
+
* @param user
|
|
61
|
+
* @param options
|
|
62
|
+
* @returns {Promise<Ability>}
|
|
63
|
+
*/
|
|
64
|
+
async generateUserAbility(user, options) {
|
|
65
|
+
const permissions = await getService('permission').findUserPermissions(user);
|
|
66
|
+
const abilityCreator = this.generateAbilityCreatorFor(user);
|
|
8
67
|
|
|
9
|
-
|
|
10
|
-
|
|
68
|
+
return abilityCreator(permissions, options);
|
|
69
|
+
},
|
|
11
70
|
|
|
12
|
-
const engine = permissions.engine
|
|
13
|
-
.new({ providers })
|
|
14
71
|
/**
|
|
15
|
-
*
|
|
72
|
+
* Create an ability factory for a specific user
|
|
73
|
+
* @param user
|
|
74
|
+
* @returns {function(*, *): Promise<Ability>}
|
|
16
75
|
*/
|
|
17
|
-
|
|
18
|
-
|
|
76
|
+
generateAbilityCreatorFor(user) {
|
|
77
|
+
return async (permissions, options) => {
|
|
78
|
+
const { can, build } = new AbilityBuilder(Ability);
|
|
79
|
+
|
|
80
|
+
for (const permission of permissions) {
|
|
81
|
+
const registerFn = this.createRegisterFunction(can, permission, user);
|
|
82
|
+
|
|
83
|
+
await this.evaluate({ permission, user, options, registerFn });
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return build({ conditionsMatcher });
|
|
87
|
+
};
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Validate, invalidate and transform the permission attributes
|
|
92
|
+
* @param {Permission} permission
|
|
93
|
+
* @returns {null|Permission}
|
|
94
|
+
*/
|
|
95
|
+
formatPermission(permission) {
|
|
96
|
+
const { actionProvider } = getService('permission');
|
|
97
|
+
|
|
98
|
+
const action = actionProvider.get(permission.action);
|
|
19
99
|
|
|
20
100
|
// If the action isn't registered into the action provider, then ignore the permission
|
|
21
101
|
if (!action) {
|
|
22
|
-
|
|
23
|
-
`Unknown action "${permission.action}" supplied when registering a new permission in engine`
|
|
24
|
-
);
|
|
25
|
-
return false;
|
|
102
|
+
return null;
|
|
26
103
|
}
|
|
27
|
-
})
|
|
28
104
|
|
|
29
|
-
/**
|
|
30
|
-
* Remove invalid properties from the permission based on the action (applyToProperties)
|
|
31
|
-
*/
|
|
32
|
-
.on('format.permission', (permission) => {
|
|
33
|
-
const action = providers.action.get(permission.action);
|
|
34
105
|
const properties = permission.properties || {};
|
|
35
106
|
|
|
36
107
|
// Only keep the properties allowed by the action (action.applyToProperties)
|
|
@@ -45,34 +116,153 @@ module.exports = (params) => {
|
|
|
45
116
|
permission
|
|
46
117
|
);
|
|
47
118
|
|
|
119
|
+
// If the `fields` property is an empty array, then ignore the permission
|
|
120
|
+
const { fields } = properties;
|
|
121
|
+
|
|
122
|
+
if (isArray(fields) && isEmpty(fields)) {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
|
|
48
126
|
return permissionWithSanitizedProperties;
|
|
49
|
-
}
|
|
127
|
+
},
|
|
50
128
|
|
|
51
129
|
/**
|
|
52
|
-
*
|
|
130
|
+
* Update the permission components through various processing
|
|
131
|
+
* @param {Permission} permission
|
|
132
|
+
* @returns {Promise<void>}
|
|
53
133
|
*/
|
|
54
|
-
|
|
55
|
-
const
|
|
134
|
+
async applyPermissionProcessors(permission) {
|
|
135
|
+
const context = createWillEvaluateContext(permission);
|
|
56
136
|
|
|
57
|
-
|
|
58
|
-
|
|
137
|
+
// 1. Trigger willEvaluatePermission hook and await transformation operated on the permission
|
|
138
|
+
await state.hooks.willEvaluatePermission.call(context);
|
|
139
|
+
},
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Register new rules using `registerFn` based on valid permission's conditions
|
|
143
|
+
* @param options {object}
|
|
144
|
+
* @param options.permission {object}
|
|
145
|
+
* @param options.user {object}
|
|
146
|
+
* @param options.options {object | undefined}
|
|
147
|
+
* @param options.registerFn {Function}
|
|
148
|
+
* @returns {Promise<void>}
|
|
149
|
+
*/
|
|
150
|
+
async evaluate(options) {
|
|
151
|
+
const { user, registerFn, options: conditionOptions } = options;
|
|
152
|
+
|
|
153
|
+
// Assert options.permission validity and format it
|
|
154
|
+
const permission = this.formatPermission(options.permission);
|
|
155
|
+
|
|
156
|
+
// If options.permission is invalid, then ignore the permission
|
|
157
|
+
if (permission === null) {
|
|
158
|
+
return;
|
|
59
159
|
}
|
|
60
|
-
});
|
|
61
160
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
161
|
+
await this.applyPermissionProcessors(permission);
|
|
162
|
+
|
|
163
|
+
// Extract the up-to-date components from the permission
|
|
164
|
+
const { action, subject, properties = {}, conditions } = permission;
|
|
165
|
+
|
|
166
|
+
// Register the permission if there is no condition
|
|
167
|
+
if (isEmpty(conditions)) {
|
|
168
|
+
return registerFn({ action, subject, fields: properties.fields });
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Set of functions used to resolve + evaluate conditions & register the permission if allowed */
|
|
172
|
+
|
|
173
|
+
// 1. Replace each condition name by its associated value
|
|
174
|
+
const resolveConditions = map(conditionProvider.get);
|
|
175
|
+
|
|
176
|
+
// 2. Filter conditions, only keep those whose handler is a function
|
|
177
|
+
const filterValidConditions = filter((condition) => isFunction(condition.handler));
|
|
178
|
+
|
|
179
|
+
// 3. Evaluate the conditions handler and returns an object
|
|
180
|
+
// containing both the original condition and its result
|
|
181
|
+
const evaluateConditions = (conditions) => {
|
|
182
|
+
return Promise.all(
|
|
183
|
+
conditions.map(async (condition) => ({
|
|
184
|
+
condition,
|
|
185
|
+
result: await condition.handler(
|
|
186
|
+
user,
|
|
187
|
+
merge(conditionOptions, { permission: cloneDeep(permission) })
|
|
188
|
+
),
|
|
189
|
+
}))
|
|
190
|
+
);
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
// 4. Only keeps booleans or objects as condition's result
|
|
194
|
+
const filterValidResults = filter(({ result }) => isBoolean(result) || isObject(result));
|
|
195
|
+
|
|
196
|
+
/**/
|
|
197
|
+
|
|
198
|
+
const evaluatedConditions = await Promise.resolve(conditions)
|
|
199
|
+
.then(resolveConditions)
|
|
200
|
+
.then(filterValidConditions)
|
|
201
|
+
.then(evaluateConditions)
|
|
202
|
+
.then(filterValidResults);
|
|
203
|
+
|
|
204
|
+
// Utils
|
|
205
|
+
const resultPropEq = propEq('result');
|
|
206
|
+
const pickResults = map(prop('result'));
|
|
207
|
+
|
|
208
|
+
if (evaluatedConditions.every(resultPropEq(false))) {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// If there is no condition or if one of them return true, register the permission as is
|
|
213
|
+
if (isEmpty(evaluatedConditions) || evaluatedConditions.some(resultPropEq(true))) {
|
|
214
|
+
return registerFn({ action, subject, fields: properties.fields });
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const results = pickResults(evaluatedConditions).filter(isObject);
|
|
218
|
+
|
|
219
|
+
if (isEmpty(results)) {
|
|
220
|
+
return registerFn({ action, subject, fields: properties.fields });
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Register the permission
|
|
224
|
+
return registerFn({
|
|
225
|
+
action,
|
|
226
|
+
subject,
|
|
227
|
+
fields: properties.fields,
|
|
228
|
+
condition: { $and: [{ $or: results }] },
|
|
229
|
+
});
|
|
65
230
|
},
|
|
66
231
|
|
|
67
232
|
/**
|
|
68
|
-
*
|
|
69
|
-
* @param
|
|
70
|
-
* @
|
|
233
|
+
* Encapsulate a register function with custom params to fit `evaluatePermission`'s syntax
|
|
234
|
+
* @param can
|
|
235
|
+
* @param {Permission} permission
|
|
236
|
+
* @param {object} user
|
|
237
|
+
* @returns {function}
|
|
71
238
|
*/
|
|
72
|
-
|
|
73
|
-
const
|
|
239
|
+
createRegisterFunction(can, permission, user) {
|
|
240
|
+
const registerToCasl = (caslPermission) => {
|
|
241
|
+
const { action, subject, fields, condition } = caslPermission;
|
|
242
|
+
|
|
243
|
+
can(
|
|
244
|
+
action,
|
|
245
|
+
isNil(subject) ? 'all' : subject,
|
|
246
|
+
fields,
|
|
247
|
+
isObject(condition) ? condition : undefined
|
|
248
|
+
);
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
const runWillRegisterHook = async (caslPermission) => {
|
|
252
|
+
const hookContext = createWillRegisterContext(caslPermission, {
|
|
253
|
+
permission,
|
|
254
|
+
user,
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
await state.hooks.willRegisterPermission.call(hookContext);
|
|
258
|
+
|
|
259
|
+
return caslPermission;
|
|
260
|
+
};
|
|
74
261
|
|
|
75
|
-
return
|
|
262
|
+
return async (caslPermission) => {
|
|
263
|
+
await runWillRegisterHook(caslPermission);
|
|
264
|
+
registerToCasl(caslPermission);
|
|
265
|
+
};
|
|
76
266
|
},
|
|
77
267
|
|
|
78
268
|
/**
|
|
@@ -10,14 +10,11 @@ const permissionQueries = require('./permission/queries');
|
|
|
10
10
|
|
|
11
11
|
const actionProvider = createActionProvider();
|
|
12
12
|
const conditionProvider = createConditionProvider();
|
|
13
|
+
const engine = createPermissionEngine(conditionProvider);
|
|
13
14
|
const sectionsBuilder = createSectionsBuilder();
|
|
14
15
|
|
|
15
16
|
const sanitizePermission = domain.sanitizePermissionFields;
|
|
16
17
|
|
|
17
|
-
const engine = createPermissionEngine({
|
|
18
|
-
providers: { action: actionProvider, condition: conditionProvider },
|
|
19
|
-
});
|
|
20
|
-
|
|
21
18
|
module.exports = {
|
|
22
19
|
// Queries / Actions
|
|
23
20
|
...permissionQueries,
|
|
@@ -33,16 +33,10 @@ const authenticate = async (ctx) => {
|
|
|
33
33
|
|
|
34
34
|
const userAbility = await getService('permission').engine.generateUserAbility(user);
|
|
35
35
|
|
|
36
|
-
// TODO: use the ability from ctx.state.auth instead of
|
|
37
|
-
// ctx.state.userAbility, and remove the assign below
|
|
38
36
|
ctx.state.userAbility = userAbility;
|
|
39
37
|
ctx.state.user = user;
|
|
40
38
|
|
|
41
|
-
return {
|
|
42
|
-
authenticated: true,
|
|
43
|
-
credentials: user,
|
|
44
|
-
ability: userAbility,
|
|
45
|
-
};
|
|
39
|
+
return { authenticated: true, credentials: user };
|
|
46
40
|
};
|
|
47
41
|
|
|
48
42
|
/** @type {import('.').AuthStrategy} */
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { castArray, isNil } = require('lodash/fp');
|
|
4
3
|
const { UnauthorizedError, ForbiddenError } = require('@strapi/utils').errors;
|
|
5
4
|
const constants = require('../services/constants');
|
|
6
5
|
const { getService } = require('../utils');
|
|
@@ -21,10 +20,7 @@ const extractToken = (ctx) => {
|
|
|
21
20
|
return null;
|
|
22
21
|
};
|
|
23
22
|
|
|
24
|
-
/**
|
|
25
|
-
* Authenticate the validity of the token
|
|
26
|
-
*
|
|
27
|
-
* @type {import('.').AuthenticateFunction} */
|
|
23
|
+
/** @type {import('.').AuthenticateFunction} */
|
|
28
24
|
const authenticate = async (ctx) => {
|
|
29
25
|
const apiTokenService = getService('api-token');
|
|
30
26
|
const token = extractToken(ctx);
|
|
@@ -37,89 +33,33 @@ const authenticate = async (ctx) => {
|
|
|
37
33
|
accessKey: apiTokenService.hash(token),
|
|
38
34
|
});
|
|
39
35
|
|
|
40
|
-
// token not found
|
|
41
36
|
if (!apiToken) {
|
|
42
37
|
return { authenticated: false };
|
|
43
38
|
}
|
|
44
39
|
|
|
45
|
-
const currentDate = new Date();
|
|
46
|
-
|
|
47
|
-
if (!isNil(apiToken.expiresAt)) {
|
|
48
|
-
const expirationDate = new Date(apiToken.expiresAt);
|
|
49
|
-
// token has expired
|
|
50
|
-
if (expirationDate < currentDate) {
|
|
51
|
-
return { authenticated: false, error: new UnauthorizedError('Token expired') };
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// update lastUsedAt
|
|
56
|
-
await apiTokenService.update(apiToken.id, {
|
|
57
|
-
lastUsedAt: currentDate,
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
if (apiToken.type === constants.API_TOKEN_TYPE.CUSTOM) {
|
|
61
|
-
const ability = await strapi.contentAPI.permissions.engine.generateAbility(
|
|
62
|
-
apiToken.permissions.map((action) => ({ action }))
|
|
63
|
-
);
|
|
64
|
-
|
|
65
|
-
return { authenticated: true, ability, credentials: apiToken };
|
|
66
|
-
}
|
|
67
|
-
|
|
68
40
|
return { authenticated: true, credentials: apiToken };
|
|
69
41
|
};
|
|
70
42
|
|
|
71
|
-
/**
|
|
72
|
-
* Verify the token has the required abilities for the requested scope
|
|
73
|
-
*
|
|
74
|
-
* @type {import('.').VerifyFunction} */
|
|
43
|
+
/** @type {import('.').VerifyFunction} */
|
|
75
44
|
const verify = (auth, config) => {
|
|
76
|
-
const { credentials: apiToken
|
|
45
|
+
const { credentials: apiToken } = auth;
|
|
77
46
|
|
|
78
47
|
if (!apiToken) {
|
|
79
|
-
throw new UnauthorizedError(
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const currentDate = new Date();
|
|
83
|
-
|
|
84
|
-
if (!isNil(apiToken.expiresAt)) {
|
|
85
|
-
const expirationDate = new Date(apiToken.expiresAt);
|
|
86
|
-
// token has expired
|
|
87
|
-
if (expirationDate < currentDate) {
|
|
88
|
-
throw new UnauthorizedError('Token expired');
|
|
89
|
-
}
|
|
48
|
+
throw new UnauthorizedError();
|
|
90
49
|
}
|
|
91
50
|
|
|
92
|
-
// Full access
|
|
93
51
|
if (apiToken.type === constants.API_TOKEN_TYPE.FULL_ACCESS) {
|
|
94
52
|
return;
|
|
95
53
|
}
|
|
96
54
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
* scopes. If the route has no scope, then you can't get access to it.
|
|
102
|
-
*/
|
|
103
|
-
const scopes = castArray(config.scope);
|
|
104
|
-
|
|
105
|
-
if (config.scope && scopes.every(isReadScope)) {
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
55
|
+
/**
|
|
56
|
+
* If you don't have `full-access` you can only access `find` and `findOne`
|
|
57
|
+
* scopes. If the route has no scope, then you can't get access to it.
|
|
58
|
+
*/
|
|
109
59
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
throw new ForbiddenError();
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const scopes = castArray(config.scope);
|
|
117
|
-
|
|
118
|
-
const isAllowed = scopes.every((scope) => ability.can(scope));
|
|
119
|
-
|
|
120
|
-
if (isAllowed) {
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
60
|
+
const scopes = Array.isArray(config.scope) ? config.scope : [config.scope];
|
|
61
|
+
if (config.scope && scopes.every(isReadScope)) {
|
|
62
|
+
return;
|
|
123
63
|
}
|
|
124
64
|
|
|
125
65
|
throw new ForbiddenError();
|
|
@@ -9,16 +9,8 @@ const apiTokenCreationSchema = yup
|
|
|
9
9
|
name: yup.string().min(1).required(),
|
|
10
10
|
description: yup.string().optional(),
|
|
11
11
|
type: yup.string().oneOf(Object.values(constants.API_TOKEN_TYPE)).required(),
|
|
12
|
-
permissions: yup.array().of(yup.string()).nullable(),
|
|
13
|
-
lifespan: yup
|
|
14
|
-
.number()
|
|
15
|
-
.integer()
|
|
16
|
-
.min(1)
|
|
17
|
-
.oneOf(Object.values(constants.API_TOKEN_LIFESPANS))
|
|
18
|
-
.nullable(),
|
|
19
12
|
})
|
|
20
|
-
.noUnknown()
|
|
21
|
-
.strict();
|
|
13
|
+
.noUnknown();
|
|
22
14
|
|
|
23
15
|
const apiTokenUpdateSchema = yup
|
|
24
16
|
.object()
|
|
@@ -26,10 +18,8 @@ const apiTokenUpdateSchema = yup
|
|
|
26
18
|
name: yup.string().min(1).notNull(),
|
|
27
19
|
description: yup.string().nullable(),
|
|
28
20
|
type: yup.string().oneOf(Object.values(constants.API_TOKEN_TYPE)).notNull(),
|
|
29
|
-
permissions: yup.array().of(yup.string()).nullable(),
|
|
30
21
|
})
|
|
31
|
-
.noUnknown()
|
|
32
|
-
.strict();
|
|
22
|
+
.noUnknown();
|
|
33
23
|
|
|
34
24
|
module.exports = {
|
|
35
25
|
validateApiTokenCreationInput: validateYupSchema(apiTokenCreationSchema),
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
import React, { memo } from 'react';
|
|
2
|
-
import PropTypes from 'prop-types';
|
|
3
|
-
import styled from 'styled-components';
|
|
4
|
-
import { pxToRem, RemoveRoundedButton, Link } from '@strapi/helper-plugin';
|
|
5
|
-
import { useIntl } from 'react-intl';
|
|
6
|
-
import { useLocation } from 'react-router-dom';
|
|
7
|
-
import has from 'lodash/has';
|
|
8
|
-
import isEmpty from 'lodash/isEmpty';
|
|
9
|
-
import { Box } from '@strapi/design-system/Box';
|
|
10
|
-
import { Flex } from '@strapi/design-system/Flex';
|
|
11
|
-
import { Typography } from '@strapi/design-system/Typography';
|
|
12
|
-
import { getTrad } from '../../utils';
|
|
13
|
-
|
|
14
|
-
const StyledBullet = styled.div`
|
|
15
|
-
width: ${pxToRem(6)};
|
|
16
|
-
height: ${pxToRem(6)};
|
|
17
|
-
background: ${({ theme, isDraft }) => theme.colors[isDraft ? 'secondary600' : 'success600']};
|
|
18
|
-
border-radius: 50%;
|
|
19
|
-
cursor: pointer;
|
|
20
|
-
`;
|
|
21
|
-
|
|
22
|
-
function ListItem({
|
|
23
|
-
data,
|
|
24
|
-
displayNavigationLink,
|
|
25
|
-
isDisabled,
|
|
26
|
-
mainField,
|
|
27
|
-
onRemove,
|
|
28
|
-
searchToPersist,
|
|
29
|
-
targetModel,
|
|
30
|
-
}) {
|
|
31
|
-
const { formatMessage } = useIntl();
|
|
32
|
-
const to = `/content-manager/collectionType/${targetModel}/${data.id}`;
|
|
33
|
-
let cursor = 'pointer';
|
|
34
|
-
|
|
35
|
-
if (isDisabled) {
|
|
36
|
-
cursor = 'not-allowed';
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
if (!displayNavigationLink) {
|
|
40
|
-
cursor = 'default';
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const hasDraftAndPublish = has(data, 'publishedAt');
|
|
44
|
-
const isDraft = isEmpty(data.publishedAt);
|
|
45
|
-
const value = data[mainField.name];
|
|
46
|
-
const draftMessage = {
|
|
47
|
-
id: getTrad('components.Select.draft-info-title'),
|
|
48
|
-
defaultMessage: 'State: Draft',
|
|
49
|
-
};
|
|
50
|
-
const publishedMessage = {
|
|
51
|
-
id: getTrad('components.Select.publish-info-title'),
|
|
52
|
-
defaultMessage: 'State: Published',
|
|
53
|
-
};
|
|
54
|
-
const title = isDraft ? formatMessage(draftMessage) : formatMessage(publishedMessage);
|
|
55
|
-
const { pathname } = useLocation();
|
|
56
|
-
|
|
57
|
-
return (
|
|
58
|
-
<Flex as="li" alignItems="center">
|
|
59
|
-
<Flex style={{ flex: 1 }} alignItems="center">
|
|
60
|
-
{hasDraftAndPublish && (
|
|
61
|
-
<Box paddingRight={2}>
|
|
62
|
-
<StyledBullet isDraft={isDraft} title={title} />
|
|
63
|
-
</Box>
|
|
64
|
-
)}
|
|
65
|
-
{displayNavigationLink ? (
|
|
66
|
-
<Link
|
|
67
|
-
to={{ pathname: to, state: { from: pathname }, search: searchToPersist }}
|
|
68
|
-
style={{ textTransform: 'none' }}
|
|
69
|
-
>
|
|
70
|
-
{value || data.id}
|
|
71
|
-
</Link>
|
|
72
|
-
) : (
|
|
73
|
-
<Typography variant="pi">{value || data.id}</Typography>
|
|
74
|
-
)}
|
|
75
|
-
</Flex>
|
|
76
|
-
<RemoveRoundedButton onClick={onRemove} label="Remove" style={{ cursor }} />
|
|
77
|
-
</Flex>
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
ListItem.defaultProps = {
|
|
82
|
-
onRemove() {},
|
|
83
|
-
searchToPersist: null,
|
|
84
|
-
targetModel: '',
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
ListItem.propTypes = {
|
|
88
|
-
data: PropTypes.object.isRequired,
|
|
89
|
-
displayNavigationLink: PropTypes.bool.isRequired,
|
|
90
|
-
isDisabled: PropTypes.bool.isRequired,
|
|
91
|
-
mainField: PropTypes.shape({
|
|
92
|
-
name: PropTypes.string.isRequired,
|
|
93
|
-
schema: PropTypes.shape({
|
|
94
|
-
type: PropTypes.string.isRequired,
|
|
95
|
-
}).isRequired,
|
|
96
|
-
}).isRequired,
|
|
97
|
-
onRemove: PropTypes.func,
|
|
98
|
-
searchToPersist: PropTypes.string,
|
|
99
|
-
targetModel: PropTypes.string,
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
export default memo(ListItem);
|