@strapi/admin 4.4.0-alpha.0 → 4.4.0-beta.1
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/DynamicTable/CellContent/RelationSingle/index.js +1 -1
- package/admin/src/content-manager/components/FieldTypeIcon/index.js +1 -31
- package/admin/src/content-manager/components/Inputs/index.js +10 -30
- package/admin/src/content-manager/components/SelectMany/index.js +3 -0
- package/admin/src/content-manager/components/SelectOne/SingleValue.js +2 -2
- package/admin/src/content-manager/components/SelectOne/index.js +3 -0
- package/admin/src/content-manager/components/SelectWrapper/Option.js +2 -2
- package/admin/src/content-manager/components/SelectWrapper/index.js +6 -0
- package/admin/src/content-manager/pages/EditSettingsView/components/FormModal.js +2 -7
- package/admin/src/content-manager/pages/EditSettingsView/index.js +1 -2
- package/admin/src/content-manager/pages/EditView/index.js +84 -91
- package/admin/src/content-manager/pages/ListSettingsView/index.js +1 -1
- package/admin/src/contexts/ApiTokenPermissions/index.js +24 -0
- package/admin/src/core/apis/index.js +0 -1
- package/admin/src/hooks/index.js +1 -0
- package/admin/src/hooks/useRegenerate/index.js +34 -0
- package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/ActionBoundRoutes/index.js +56 -0
- package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/BoundRoute/getMethodColor.js +41 -0
- package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/BoundRoute/index.js +72 -0
- package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/CollapsableContentType/CheckBoxWrapper.js +30 -0
- package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/CollapsableContentType/index.js +150 -0
- package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/ContenTypesSection/index.js +37 -0
- package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/Permissions/index.js +40 -0
- package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/Regenerate/index.js +68 -0
- package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/index.js +452 -180
- package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/init.js +13 -0
- package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/reducer.js +55 -0
- package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/utils/getDateOfExpiration.js +16 -0
- package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/utils/index.js +5 -0
- package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/utils/schema.js +2 -1
- package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/utils/transformPermissionsData.js +36 -0
- package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/DefaultButton/index.js +63 -0
- package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/DeleteButton/index.js +1 -0
- package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/ReadButton/index.js +19 -0
- package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/UpdateButton/index.js +3 -36
- package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/index.js +13 -11
- package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/index.js +4 -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/pages/SettingsPage/pages/Roles/ListPage/index.js +1 -1
- package/admin/src/pages/SettingsPage/pages/Users/ListPage/index.js +1 -1
- package/admin/src/pages/SettingsPage/pages/Webhooks/EditView/components/EventInput/index.js +10 -10
- package/admin/src/pages/SettingsPage/pages/Webhooks/ListView/index.js +1 -1
- package/admin/src/permissions/defaultPermissions.js +2 -6
- package/admin/src/translations/en.json +18 -0
- package/admin/src/translations/es.json +1 -1
- package/admin/src/translations/fr.json +33 -0
- package/build/1669.d1b29c28.chunk.js +1 -0
- package/build/{1856.d3da2fcd.chunk.js → 1856.47226450.chunk.js} +6 -6
- package/build/{2077.b25a0b57.chunk.js → 2077.61cebc93.chunk.js} +4 -4
- package/build/{2912.da8a70aa.chunk.js → 2912.a015078a.chunk.js} +10 -10
- package/build/4235.982b5799.chunk.js +30 -0
- package/build/{4715.3787be19.chunk.js → 4715.3f6cac0a.chunk.js} +30 -30
- package/build/{4800.d3ebc81d.chunk.js → 4800.d09f1225.chunk.js} +1 -1
- package/build/{4982.a4e36c9a.chunk.js → 4982.c6f88c5d.chunk.js} +8 -8
- package/build/611.a91aff91.chunk.js +158 -0
- package/build/7379.d246dd38.chunk.js +1 -0
- package/build/{7841.922b96eb.chunk.js → 7841.91f793dc.chunk.js} +9 -9
- package/build/{7866.22e3c9f8.chunk.js → 7866.c793a31d.chunk.js} +23 -23
- package/build/{8380.ab3939f3.chunk.js → 8380.8789ff76.chunk.js} +8 -8
- package/build/{8549.0e30f86d.chunk.js → 8549.133c4473.chunk.js} +5 -5
- package/build/{8773.4e36117f.chunk.js → 8773.eccaa5f3.chunk.js} +9 -9
- package/build/{9066.2847fdff.chunk.js → 9066.08049eb1.chunk.js} +4 -4
- package/build/{9166.280c7521.chunk.js → 9166.037339e0.chunk.js} +2 -2
- package/build/{9420.ee1ccff7.chunk.js → 9420.43a86e7c.chunk.js} +30 -30
- package/build/{Admin-authenticatedApp.aaa66872.chunk.js → Admin-authenticatedApp.3a31a087.chunk.js} +3 -3
- package/build/{Admin_homePage.118926e0.chunk.js → Admin_homePage.6d5e3236.chunk.js} +1 -1
- package/build/{Admin_marketplace.2d181ad7.chunk.js → Admin_marketplace.82c0570b.chunk.js} +1 -1
- package/build/{Admin_profilePage.8617313a.chunk.js → Admin_profilePage.83991a6c.chunk.js} +1 -1
- package/build/{Admin_settingsPage.e58753c8.chunk.js → Admin_settingsPage.fc9c607a.chunk.js} +16 -16
- package/build/admin-app.41b6472c.chunk.js +112 -0
- package/build/admin-edit-roles-page.4dd6bcb9.chunk.js +1 -0
- package/build/{admin-users.1d0aa7a0.chunk.js → admin-users.dccd5f4c.chunk.js} +2 -2
- package/build/api-tokens-create-page.29cc87b6.chunk.js +1 -0
- package/build/api-tokens-edit-page.c294a88f.chunk.js +1 -0
- package/build/api-tokens-list-page.bb36535f.chunk.js +16 -0
- package/build/content-manager.fb5ee865.chunk.js +1178 -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.42cecba9.chunk.js +142 -0
- package/build/{email-settings-page.818761d5.chunk.js → email-settings-page.64037147.chunk.js} +5 -5
- package/build/en-json.a9918c93.chunk.js +1 -0
- package/build/{es-json.bb1fc425.chunk.js → es-json.3a9c7c09.chunk.js} +1 -1
- package/build/fr-json.4ed1fc2c.chunk.js +1 -0
- package/build/{i18n-settings-page.bf1304b0.chunk.js → i18n-settings-page.0b73785d.chunk.js} +5 -5
- package/build/index.html +1 -1
- package/build/{main.7db3414f.js → main.cdfda31e.js} +1235 -1235
- package/build/{runtime~main.c1c5510b.js → runtime~main.fa8f8898.js} +1 -1
- package/build/sso-settings-page.9ceb0140.chunk.js +1 -0
- package/build/{upload-settings.5dfe0fe2.chunk.js → upload-settings.80ff0974.chunk.js} +7 -7
- package/build/upload-translation-ca-json.db8ed7ba.chunk.js +1 -0
- package/build/{users-advanced-settings-page.f11c8af4.chunk.js → users-advanced-settings-page.a02f4806.chunk.js} +5 -5
- package/build/{users-roles-settings-page.cafb4fe5.chunk.js → users-roles-settings-page.b33ec5e5.chunk.js} +1 -1
- package/build/{webhook-edit-page.9aba79b2.chunk.js → webhook-edit-page.9e46fc3f.chunk.js} +3 -3
- package/build/{webhook-list-page.912becb8.chunk.js → webhook-list-page.2775a683.chunk.js} +5 -5
- package/ee/admin/pages/SettingsPage/pages/Roles/ListPage/index.js +1 -1
- package/package.json +14 -13
- package/scripts/build.js +2 -4
- package/server/bootstrap.js +19 -1
- package/server/config/admin-actions.js +20 -0
- package/server/content-types/api-token-permission.js +36 -0
- package/server/content-types/api-token.js +25 -1
- package/server/content-types/index.js +1 -0
- package/server/controllers/api-token.js +24 -1
- package/server/controllers/content-api.js +15 -0
- package/server/controllers/index.js +1 -0
- package/server/routes/api-tokens.js +11 -0
- package/server/routes/content-api.js +20 -0
- package/server/routes/index.js +2 -0
- package/server/services/api-token.js +310 -29
- package/server/services/constants.js +10 -0
- package/server/services/permission/engine.js +36 -226
- package/server/services/permission/permissions-manager/query-builers.js +3 -2
- package/server/services/permission/queries.js +1 -1
- package/server/services/permission.js +4 -1
- package/server/strategies/admin.js +7 -1
- package/server/strategies/api-token.js +71 -11
- package/server/validation/api-tokens.js +12 -2
- package/server/validation/common-functions/check-fields-are-correctly-nested.js +1 -1
- package/admin/src/core/apis/CustomFields.js +0 -80
- package/build/1669.4ce92b2f.chunk.js +0 -1
- package/build/524.2437fb56.chunk.js +0 -644
- package/build/admin-app.1f9e13f8.chunk.js +0 -112
- package/build/admin-edit-roles-page.554ba3fa.chunk.js +0 -1
- package/build/api-tokens-create-page.b4a9987d.chunk.js +0 -1
- package/build/api-tokens-edit-page.6f5b4e26.chunk.js +0 -1
- package/build/api-tokens-list-page.06938769.chunk.js +0 -15
- package/build/content-manager.86f7594d.chunk.js +0 -1178
- package/build/content-type-builder-list-view.9b874fd4.chunk.js +0 -194
- package/build/content-type-builder-translation-en-json.f985c9c4.chunk.js +0 -1
- package/build/content-type-builder.47ab07ad.chunk.js +0 -145
- package/build/en-json.1bf20384.chunk.js +0 -1
- package/build/fr-json.a3cf2e0b.chunk.js +0 -1
- package/build/sso-settings-page.445184e0.chunk.js +0 -1
- package/build/upload-translation-ca-json.00dc1f33.chunk.js +0 -1
- package/server/services/permission/engine-hooks.js +0 -82
|
@@ -1,107 +1,36 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const {
|
|
4
|
-
|
|
5
|
-
|
|
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');
|
|
3
|
+
const { curry, isArray, isEmpty, difference } = require('lodash/fp');
|
|
4
|
+
const permissions = require('@strapi/permissions');
|
|
5
|
+
|
|
22
6
|
const permissionDomain = require('../../domain/permission/index');
|
|
23
7
|
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);
|
|
67
8
|
|
|
68
|
-
|
|
69
|
-
|
|
9
|
+
module.exports = (params) => {
|
|
10
|
+
const { providers } = params;
|
|
70
11
|
|
|
12
|
+
const engine = permissions.engine
|
|
13
|
+
.new({ providers })
|
|
71
14
|
/**
|
|
72
|
-
*
|
|
73
|
-
* @param user
|
|
74
|
-
* @returns {function(*, *): Promise<Ability>}
|
|
15
|
+
* Validate the permission's action exists in the action registry
|
|
75
16
|
*/
|
|
76
|
-
|
|
77
|
-
|
|
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);
|
|
17
|
+
.on('before-format::validate.permission', ({ permission }) => {
|
|
18
|
+
const action = providers.action.get(permission.action);
|
|
99
19
|
|
|
100
20
|
// If the action isn't registered into the action provider, then ignore the permission
|
|
101
21
|
if (!action) {
|
|
102
|
-
|
|
22
|
+
strapi.log.debug(
|
|
23
|
+
`Unknown action "${permission.action}" supplied when registering a new permission in engine`
|
|
24
|
+
);
|
|
25
|
+
return false;
|
|
103
26
|
}
|
|
27
|
+
})
|
|
104
28
|
|
|
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);
|
|
105
34
|
const properties = permission.properties || {};
|
|
106
35
|
|
|
107
36
|
// Only keep the properties allowed by the action (action.applyToProperties)
|
|
@@ -116,153 +45,34 @@ module.exports = (conditionProvider) => {
|
|
|
116
45
|
permission
|
|
117
46
|
);
|
|
118
47
|
|
|
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
|
-
|
|
126
48
|
return permissionWithSanitizedProperties;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Update the permission components through various processing
|
|
131
|
-
* @param {Permission} permission
|
|
132
|
-
* @returns {Promise<void>}
|
|
133
|
-
*/
|
|
134
|
-
async applyPermissionProcessors(permission) {
|
|
135
|
-
const context = createWillEvaluateContext(permission);
|
|
136
|
-
|
|
137
|
-
// 1. Trigger willEvaluatePermission hook and await transformation operated on the permission
|
|
138
|
-
await state.hooks.willEvaluatePermission.call(context);
|
|
139
|
-
},
|
|
49
|
+
})
|
|
140
50
|
|
|
141
51
|
/**
|
|
142
|
-
*
|
|
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>}
|
|
52
|
+
* Ignore the permission if the fields property is an empty array (access to no field)
|
|
149
53
|
*/
|
|
150
|
-
|
|
151
|
-
const {
|
|
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;
|
|
159
|
-
}
|
|
160
|
-
|
|
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);
|
|
54
|
+
.on('after-format::validate.permission', ({ permission }) => {
|
|
55
|
+
const { fields } = permission.properties;
|
|
203
56
|
|
|
204
|
-
|
|
205
|
-
|
|
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 });
|
|
57
|
+
if (isArray(fields) && isEmpty(fields)) {
|
|
58
|
+
return false;
|
|
221
59
|
}
|
|
60
|
+
});
|
|
222
61
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
subject,
|
|
227
|
-
fields: properties.fields,
|
|
228
|
-
condition: { $and: [{ $or: results }] },
|
|
229
|
-
});
|
|
62
|
+
return {
|
|
63
|
+
get hooks() {
|
|
64
|
+
return engine.hooks;
|
|
230
65
|
},
|
|
231
66
|
|
|
232
67
|
/**
|
|
233
|
-
*
|
|
234
|
-
* @param
|
|
235
|
-
* @
|
|
236
|
-
* @param {object} user
|
|
237
|
-
* @returns {function}
|
|
68
|
+
* Generate an ability based on the given user (using associated roles & permissions)
|
|
69
|
+
* @param user
|
|
70
|
+
* @returns {Promise<Ability>}
|
|
238
71
|
*/
|
|
239
|
-
|
|
240
|
-
const
|
|
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
|
-
};
|
|
72
|
+
async generateUserAbility(user) {
|
|
73
|
+
const permissions = await getService('permission').findUserPermissions(user);
|
|
261
74
|
|
|
262
|
-
return
|
|
263
|
-
await runWillRegisterHook(caslPermission);
|
|
264
|
-
registerToCasl(caslPermission);
|
|
265
|
-
};
|
|
75
|
+
return engine.generateAbility(permissions, user);
|
|
266
76
|
},
|
|
267
77
|
|
|
268
78
|
/**
|
|
@@ -49,9 +49,10 @@ const unwrapDeep = (obj) => {
|
|
|
49
49
|
|
|
50
50
|
if (_.isPlainObject(v)) {
|
|
51
51
|
if ('$elemMatch' in v) {
|
|
52
|
-
|
|
52
|
+
_.setWith(acc, key, unwrapDeep(v.$elemMatch));
|
|
53
|
+
} else {
|
|
54
|
+
_.setWith(acc, key, unwrapDeep(v));
|
|
53
55
|
}
|
|
54
|
-
_.setWith(acc, key, unwrapDeep(v));
|
|
55
56
|
} else if (_.isArray(v)) {
|
|
56
57
|
// prettier-ignore
|
|
57
58
|
_.setWith(acc, key, v.map(v => unwrapDeep(v)));
|
|
@@ -144,7 +144,7 @@ const cleanPermissionsInDatabase = async () => {
|
|
|
144
144
|
const total = await strapi.query('admin::permission').count();
|
|
145
145
|
const pageCount = Math.ceil(total / pageSize);
|
|
146
146
|
|
|
147
|
-
for (let page = 0; page < pageCount; page
|
|
147
|
+
for (let page = 0; page < pageCount; page += 1) {
|
|
148
148
|
// 1. Find invalid permissions and collect their ID to delete them later
|
|
149
149
|
const results = await strapi
|
|
150
150
|
.query('admin::permission')
|
|
@@ -10,11 +10,14 @@ const permissionQueries = require('./permission/queries');
|
|
|
10
10
|
|
|
11
11
|
const actionProvider = createActionProvider();
|
|
12
12
|
const conditionProvider = createConditionProvider();
|
|
13
|
-
const engine = createPermissionEngine(conditionProvider);
|
|
14
13
|
const sectionsBuilder = createSectionsBuilder();
|
|
15
14
|
|
|
16
15
|
const sanitizePermission = domain.sanitizePermissionFields;
|
|
17
16
|
|
|
17
|
+
const engine = createPermissionEngine({
|
|
18
|
+
providers: { action: actionProvider, condition: conditionProvider },
|
|
19
|
+
});
|
|
20
|
+
|
|
18
21
|
module.exports = {
|
|
19
22
|
// Queries / Actions
|
|
20
23
|
...permissionQueries,
|
|
@@ -33,10 +33,16 @@ 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
|
|
36
38
|
ctx.state.userAbility = userAbility;
|
|
37
39
|
ctx.state.user = user;
|
|
38
40
|
|
|
39
|
-
return {
|
|
41
|
+
return {
|
|
42
|
+
authenticated: true,
|
|
43
|
+
credentials: user,
|
|
44
|
+
ability: userAbility,
|
|
45
|
+
};
|
|
40
46
|
};
|
|
41
47
|
|
|
42
48
|
/** @type {import('.').AuthStrategy} */
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { castArray, isNil } = require('lodash/fp');
|
|
3
4
|
const { UnauthorizedError, ForbiddenError } = require('@strapi/utils').errors;
|
|
4
5
|
const constants = require('../services/constants');
|
|
5
6
|
const { getService } = require('../utils');
|
|
@@ -20,7 +21,10 @@ const extractToken = (ctx) => {
|
|
|
20
21
|
return null;
|
|
21
22
|
};
|
|
22
23
|
|
|
23
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* Authenticate the validity of the token
|
|
26
|
+
*
|
|
27
|
+
* @type {import('.').AuthenticateFunction} */
|
|
24
28
|
const authenticate = async (ctx) => {
|
|
25
29
|
const apiTokenService = getService('api-token');
|
|
26
30
|
const token = extractToken(ctx);
|
|
@@ -33,33 +37,89 @@ const authenticate = async (ctx) => {
|
|
|
33
37
|
accessKey: apiTokenService.hash(token),
|
|
34
38
|
});
|
|
35
39
|
|
|
40
|
+
// token not found
|
|
36
41
|
if (!apiToken) {
|
|
37
42
|
return { authenticated: false };
|
|
38
43
|
}
|
|
39
44
|
|
|
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
|
+
|
|
40
68
|
return { authenticated: true, credentials: apiToken };
|
|
41
69
|
};
|
|
42
70
|
|
|
43
|
-
/**
|
|
71
|
+
/**
|
|
72
|
+
* Verify the token has the required abilities for the requested scope
|
|
73
|
+
*
|
|
74
|
+
* @type {import('.').VerifyFunction} */
|
|
44
75
|
const verify = (auth, config) => {
|
|
45
|
-
const { credentials: apiToken } = auth;
|
|
76
|
+
const { credentials: apiToken, ability } = auth;
|
|
46
77
|
|
|
47
78
|
if (!apiToken) {
|
|
48
|
-
throw new UnauthorizedError();
|
|
79
|
+
throw new UnauthorizedError('Token not found');
|
|
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
|
+
}
|
|
49
90
|
}
|
|
50
91
|
|
|
92
|
+
// Full access
|
|
51
93
|
if (apiToken.type === constants.API_TOKEN_TYPE.FULL_ACCESS) {
|
|
52
94
|
return;
|
|
53
95
|
}
|
|
54
96
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
97
|
+
// Read only
|
|
98
|
+
if (apiToken.type === constants.API_TOKEN_TYPE.READ_ONLY) {
|
|
99
|
+
/**
|
|
100
|
+
* If you don't have `full-access` you can only access `find` and `findOne`
|
|
101
|
+
* scopes. If the route has no scope, then you can't get access to it.
|
|
102
|
+
*/
|
|
103
|
+
const scopes = castArray(config.scope);
|
|
59
104
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
105
|
+
if (config.scope && scopes.every(isReadScope)) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Custom
|
|
111
|
+
else if (apiToken.type === constants.API_TOKEN_TYPE.CUSTOM) {
|
|
112
|
+
if (!ability) {
|
|
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
|
+
}
|
|
63
123
|
}
|
|
64
124
|
|
|
65
125
|
throw new ForbiddenError();
|
|
@@ -9,8 +9,16 @@ 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(),
|
|
12
19
|
})
|
|
13
|
-
.noUnknown()
|
|
20
|
+
.noUnknown()
|
|
21
|
+
.strict();
|
|
14
22
|
|
|
15
23
|
const apiTokenUpdateSchema = yup
|
|
16
24
|
.object()
|
|
@@ -18,8 +26,10 @@ const apiTokenUpdateSchema = yup
|
|
|
18
26
|
name: yup.string().min(1).notNull(),
|
|
19
27
|
description: yup.string().nullable(),
|
|
20
28
|
type: yup.string().oneOf(Object.values(constants.API_TOKEN_TYPE)).notNull(),
|
|
29
|
+
permissions: yup.array().of(yup.string()).nullable(),
|
|
21
30
|
})
|
|
22
|
-
.noUnknown()
|
|
31
|
+
.noUnknown()
|
|
32
|
+
.strict();
|
|
23
33
|
|
|
24
34
|
module.exports = {
|
|
25
35
|
validateApiTokenCreationInput: validateYupSchema(apiTokenCreationSchema),
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import invariant from 'invariant';
|
|
2
|
-
|
|
3
|
-
const ALLOWED_TYPES = [
|
|
4
|
-
'biginteger',
|
|
5
|
-
'boolean',
|
|
6
|
-
'date',
|
|
7
|
-
'datetime',
|
|
8
|
-
'decimal',
|
|
9
|
-
'email',
|
|
10
|
-
'enumeration',
|
|
11
|
-
'float',
|
|
12
|
-
'integer',
|
|
13
|
-
'json',
|
|
14
|
-
'password',
|
|
15
|
-
'richtext',
|
|
16
|
-
'string',
|
|
17
|
-
'text',
|
|
18
|
-
'time',
|
|
19
|
-
'timestamp',
|
|
20
|
-
'uid',
|
|
21
|
-
];
|
|
22
|
-
|
|
23
|
-
class CustomFields {
|
|
24
|
-
constructor() {
|
|
25
|
-
this.customFields = {};
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
register(customFields) {
|
|
29
|
-
if (Array.isArray(customFields)) {
|
|
30
|
-
// If several custom fields are passed, register them one by one
|
|
31
|
-
customFields.forEach(customField => {
|
|
32
|
-
this.register(customField);
|
|
33
|
-
});
|
|
34
|
-
} else {
|
|
35
|
-
// Handle individual custom field
|
|
36
|
-
const { name, pluginId, type, intlLabel, intlDescription, components } = customFields;
|
|
37
|
-
|
|
38
|
-
// Ensure required attributes are provided
|
|
39
|
-
invariant(name, 'A name must be provided');
|
|
40
|
-
invariant(type, 'A type must be provided');
|
|
41
|
-
invariant(intlLabel, 'An intlLabel must be provided');
|
|
42
|
-
invariant(intlDescription, 'An intlDescription must be provided');
|
|
43
|
-
invariant(components, 'A components object must be provided');
|
|
44
|
-
invariant(components.Input, 'An Input component must be provided');
|
|
45
|
-
|
|
46
|
-
// Ensure the type is valid
|
|
47
|
-
invariant(
|
|
48
|
-
ALLOWED_TYPES.includes(type),
|
|
49
|
-
`Custom field type: '${type}' is not a valid Strapi type or it can't be used with a Custom Field`
|
|
50
|
-
);
|
|
51
|
-
|
|
52
|
-
// Ensure name has no special characters
|
|
53
|
-
const isValidObjectKey = /^(?![0-9])[a-zA-Z0-9$_-]+$/g;
|
|
54
|
-
invariant(
|
|
55
|
-
isValidObjectKey.test(name),
|
|
56
|
-
`Custom field name: '${name}' is not a valid object key`
|
|
57
|
-
);
|
|
58
|
-
|
|
59
|
-
// When no plugin is specified, default to the global namespace
|
|
60
|
-
const uid = pluginId ? `plugin::${pluginId}.${name}` : `global::${name}`;
|
|
61
|
-
|
|
62
|
-
// Ensure the uid is unique
|
|
63
|
-
const uidAlreadyUsed = Object.prototype.hasOwnProperty.call(this.customFields, uid);
|
|
64
|
-
invariant(!uidAlreadyUsed, `Custom field: '${uid}' has already been registered`);
|
|
65
|
-
|
|
66
|
-
this.customFields[uid] = customFields;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
getAll() {
|
|
71
|
-
return this.customFields;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
get(uid) {
|
|
75
|
-
return this.customFields[uid];
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Export an instance since it's a singleton
|
|
80
|
-
export default new CustomFields();
|