@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.
Files changed (203) hide show
  1. package/admin/src/StrapiApp.js +12 -4
  2. package/admin/src/components/Providers/index.js +10 -14
  3. package/admin/src/content-manager/components/CollectionTypeFormWrapper/index.js +11 -1
  4. package/admin/src/content-manager/components/DynamicTable/CellContent/RelationMultiple/index.js +11 -7
  5. package/admin/src/content-manager/components/DynamicTable/CellContent/index.js +6 -5
  6. package/admin/src/content-manager/components/DynamicTable/TableRows/index.js +5 -0
  7. package/admin/src/content-manager/components/DynamicTable/index.js +1 -1
  8. package/admin/src/content-manager/components/EditViewDataManagerProvider/index.js +23 -17
  9. package/admin/src/content-manager/components/EditViewDataManagerProvider/reducer.js +123 -24
  10. package/admin/src/content-manager/components/EditViewDataManagerProvider/utils/cleanData.js +17 -1
  11. package/admin/src/content-manager/components/FieldTypeIcon/index.js +1 -31
  12. package/admin/src/content-manager/components/Inputs/index.js +22 -36
  13. package/admin/src/content-manager/components/RelationInput/RelationInput.js +364 -0
  14. package/admin/src/content-manager/components/{SelectWrapper → RelationInput/components}/Option.js +15 -25
  15. package/admin/src/content-manager/components/RelationInput/components/Relation.js +48 -0
  16. package/admin/src/content-manager/components/RelationInput/components/RelationItem.js +52 -0
  17. package/admin/src/content-manager/components/RelationInput/components/RelationList.js +52 -0
  18. package/admin/src/content-manager/components/RelationInput/constants.js +1 -0
  19. package/admin/src/content-manager/components/RelationInput/index.js +1 -0
  20. package/admin/src/content-manager/components/RelationInputDataManager/RelationInputDataManager.js +250 -0
  21. package/admin/src/content-manager/components/RelationInputDataManager/constants.js +8 -0
  22. package/admin/src/content-manager/components/RelationInputDataManager/index.js +1 -0
  23. package/admin/src/content-manager/components/{SelectWrapper → RelationInputDataManager}/utils/connect.js +0 -1
  24. package/admin/src/content-manager/components/RelationInputDataManager/utils/getRelationLink.js +5 -0
  25. package/admin/src/content-manager/components/{SelectWrapper → RelationInputDataManager}/utils/index.js +1 -0
  26. package/admin/src/content-manager/components/RelationInputDataManager/utils/normalizeRelations.js +58 -0
  27. package/admin/src/content-manager/components/{SelectWrapper → RelationInputDataManager}/utils/select.js +31 -1
  28. package/admin/src/content-manager/components/SingleTypeFormWrapper/index.js +19 -2
  29. package/admin/src/content-manager/hooks/useFetchContentTypeLayout/utils/formatLayouts.js +7 -69
  30. package/admin/src/content-manager/hooks/useRelation/index.js +1 -0
  31. package/admin/src/content-manager/hooks/useRelation/useRelation.js +73 -0
  32. package/admin/src/content-manager/pages/EditSettingsView/components/DisplayedFields.js +4 -4
  33. package/admin/src/content-manager/pages/EditSettingsView/components/FormModal.js +2 -7
  34. package/admin/src/content-manager/pages/EditSettingsView/index.js +22 -51
  35. package/admin/src/content-manager/pages/EditSettingsView/reducer.js +0 -25
  36. package/admin/src/content-manager/pages/EditView/Header/index.js +3 -90
  37. package/admin/src/content-manager/pages/EditView/Header/utils/index.js +0 -1
  38. package/admin/src/content-manager/pages/EditView/Header/utils/select.js +0 -2
  39. package/admin/src/content-manager/pages/EditView/index.js +93 -155
  40. package/admin/src/content-manager/pages/ListView/FieldPicker/index.js +0 -1
  41. package/admin/src/content-manager/pages/ListView/index.js +0 -1
  42. package/admin/src/content-manager/pages/ListViewLayoutManager/index.js +0 -1
  43. package/admin/src/content-manager/utils/formatLayoutToApi.js +1 -3
  44. package/admin/src/core/apis/index.js +0 -1
  45. package/admin/src/hooks/index.js +0 -1
  46. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/index.js +197 -215
  47. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/utils/schema.js +1 -2
  48. package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/DeleteButton/index.js +0 -1
  49. package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/UpdateButton/index.js +36 -3
  50. package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/index.js +11 -13
  51. package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/index.js +2 -3
  52. package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/utils/tableHeaders.js +8 -8
  53. package/admin/src/pages/SettingsPage/pages/ApiTokens/ProtectedEditView/index.js +1 -1
  54. package/admin/src/permissions/defaultPermissions.js +6 -2
  55. package/admin/src/translations/ar.json +0 -1
  56. package/admin/src/translations/ca.json +0 -9
  57. package/admin/src/translations/cs.json +0 -2
  58. package/admin/src/translations/de.json +0 -11
  59. package/admin/src/translations/dk.json +0 -11
  60. package/admin/src/translations/en.json +0 -28
  61. package/admin/src/translations/es.json +0 -11
  62. package/admin/src/translations/fr.json +0 -11
  63. package/admin/src/translations/gu.json +0 -11
  64. package/admin/src/translations/hi.json +0 -11
  65. package/admin/src/translations/hu.json +0 -10
  66. package/admin/src/translations/id.json +0 -10
  67. package/admin/src/translations/it.json +0 -10
  68. package/admin/src/translations/ja.json +0 -11
  69. package/admin/src/translations/ko.json +0 -11
  70. package/admin/src/translations/ml.json +0 -11
  71. package/admin/src/translations/ms.json +0 -2
  72. package/admin/src/translations/nl.json +0 -11
  73. package/admin/src/translations/pl.json +0 -11
  74. package/admin/src/translations/pt-BR.json +0 -11
  75. package/admin/src/translations/pt.json +0 -1
  76. package/admin/src/translations/ru.json +0 -10
  77. package/admin/src/translations/sa.json +0 -11
  78. package/admin/src/translations/sk.json +0 -10
  79. package/admin/src/translations/sv.json +0 -9
  80. package/admin/src/translations/th.json +0 -2
  81. package/admin/src/translations/tr.json +0 -1
  82. package/admin/src/translations/uk.json +0 -2
  83. package/admin/src/translations/vi.json +0 -1
  84. package/admin/src/translations/zh-Hans.json +0 -12
  85. package/admin/src/translations/zh.json +0 -11
  86. package/build/7098.40dcd7bf.chunk.js +1 -0
  87. package/build/8851.e4ac62f2.chunk.js +158 -0
  88. package/build/{8773.c06c24c0.chunk.js → 9311.7cc03f29.chunk.js} +291 -108
  89. package/build/{Admin-authenticatedApp.9dec5230.chunk.js → Admin-authenticatedApp.e39f36c9.chunk.js} +3 -3
  90. package/build/{Admin_homePage.6d5e3236.chunk.js → Admin_homePage.118926e0.chunk.js} +1 -1
  91. package/build/{Admin_profilePage.da32abbc.chunk.js → Admin_profilePage.9d50ac44.chunk.js} +1 -1
  92. package/build/{Admin_settingsPage.98e2a62b.chunk.js → Admin_settingsPage.98a711e5.chunk.js} +16 -16
  93. package/build/admin-app.4f7618a9.chunk.js +112 -0
  94. package/build/admin-edit-roles-page.554ba3fa.chunk.js +1 -0
  95. package/build/api-tokens-create-page.4c262d6e.chunk.js +1 -0
  96. package/build/api-tokens-edit-page.10a9d368.chunk.js +1 -0
  97. package/build/api-tokens-list-page.442c9f3c.chunk.js +15 -0
  98. package/build/{ar-json.d4cb26d9.chunk.js → ar-json.3489463d.chunk.js} +1 -1
  99. package/build/{ca-json.d16c1d28.chunk.js → ca-json.a16899ae.chunk.js} +1 -1
  100. package/build/content-manager.7d57c9d1.chunk.js +1200 -0
  101. package/build/content-type-builder-list-view.8cc534e0.chunk.js +194 -0
  102. package/build/content-type-builder-translation-en-json.201bfb78.chunk.js +1 -0
  103. package/build/content-type-builder.684df7a4.chunk.js +142 -0
  104. package/build/{cs-json.c8f28ba8.chunk.js → cs-json.ce49da5c.chunk.js} +1 -1
  105. package/build/{de-json.a9b514dc.chunk.js → de-json.aa6026b3.chunk.js} +1 -1
  106. package/build/{dk-json.09e8d145.chunk.js → dk-json.fac2bcfb.chunk.js} +1 -1
  107. package/build/en-json.0c69c7d7.chunk.js +1 -0
  108. package/build/{es-json.3a9c7c09.chunk.js → es-json.d672e181.chunk.js} +1 -1
  109. package/build/{fr-json.4ed1fc2c.chunk.js → fr-json.71a16175.chunk.js} +1 -1
  110. package/build/{gu-json.d8311297.chunk.js → gu-json.ca345cd1.chunk.js} +1 -1
  111. package/build/{hi-json.0edb8d29.chunk.js → hi-json.50c7e6d4.chunk.js} +1 -1
  112. package/build/{hu-json.7855529a.chunk.js → hu-json.e0521dcc.chunk.js} +1 -1
  113. package/build/{id-json.df9618f2.chunk.js → id-json.4b1ff8d6.chunk.js} +1 -1
  114. package/build/index.html +1 -1
  115. package/build/{it-json.a21bf078.chunk.js → it-json.86bac220.chunk.js} +1 -1
  116. package/build/{ja-json.7b0d9067.chunk.js → ja-json.4e44e36b.chunk.js} +1 -1
  117. package/build/{ko-json.983c1f8f.chunk.js → ko-json.1003756e.chunk.js} +1 -1
  118. package/build/main.b47db1a3.js +9337 -0
  119. package/build/{ml-json.8dd021c8.chunk.js → ml-json.c7774425.chunk.js} +1 -1
  120. package/build/{ms-json.836ed013.chunk.js → ms-json.ed51e902.chunk.js} +1 -1
  121. package/build/{nl-json.29d2eb37.chunk.js → nl-json.f58ea235.chunk.js} +1 -1
  122. package/build/{pl-json.1f04f00c.chunk.js → pl-json.fed96aba.chunk.js} +1 -1
  123. package/build/{pt-BR-json.b4bc8efe.chunk.js → pt-BR-json.073799ab.chunk.js} +1 -1
  124. package/build/{pt-json.c23020ab.chunk.js → pt-json.3161ca22.chunk.js} +1 -1
  125. package/build/{ru-json.7ab40ccf.chunk.js → ru-json.7ad2cbbf.chunk.js} +1 -1
  126. package/build/{runtime~main.4204f341.js → runtime~main.feeac6d3.js} +1 -1
  127. package/build/{sa-json.c5a9f4ea.chunk.js → sa-json.f0f704f0.chunk.js} +1 -1
  128. package/build/{sk-json.e4c24c4e.chunk.js → sk-json.a848961b.chunk.js} +1 -1
  129. package/build/sso-settings-page.445184e0.chunk.js +1 -0
  130. package/build/{sv-json.c3f471ae.chunk.js → sv-json.b038acbe.chunk.js} +1 -1
  131. package/build/{th-json.a59ffb32.chunk.js → th-json.72e8de3d.chunk.js} +1 -1
  132. package/build/{tr-json.276e59fe.chunk.js → tr-json.9c44ea0c.chunk.js} +1 -1
  133. package/build/{uk-json.5b5b9c27.chunk.js → uk-json.c4cd2e24.chunk.js} +1 -1
  134. package/build/{vi-json.bf3424be.chunk.js → vi-json.f7890025.chunk.js} +1 -1
  135. package/build/{webhook-edit-page.9e46fc3f.chunk.js → webhook-edit-page.d2ea3351.chunk.js} +1 -1
  136. package/build/{zh-Hans-json.9c99f8d4.chunk.js → zh-Hans-json.03d2bda1.chunk.js} +1 -1
  137. package/build/{zh-json.451a0271.chunk.js → zh-json.3d0cc664.chunk.js} +1 -1
  138. package/package.json +7 -8
  139. package/server/bootstrap.js +1 -19
  140. package/server/config/admin-actions.js +0 -20
  141. package/server/content-types/api-token.js +1 -25
  142. package/server/content-types/index.js +0 -1
  143. package/server/controllers/api-token.js +1 -24
  144. package/server/controllers/index.js +0 -1
  145. package/server/routes/api-tokens.js +0 -11
  146. package/server/routes/index.js +0 -2
  147. package/server/services/api-token.js +29 -310
  148. package/server/services/constants.js +0 -10
  149. package/server/services/permission/engine-hooks.js +82 -0
  150. package/server/services/permission/engine.js +226 -36
  151. package/server/services/permission.js +1 -4
  152. package/server/strategies/admin.js +1 -7
  153. package/server/strategies/api-token.js +11 -71
  154. package/server/validation/api-tokens.js +2 -12
  155. package/admin/src/content-manager/components/SelectMany/ListItem.js +0 -102
  156. package/admin/src/content-manager/components/SelectMany/index.js +0 -148
  157. package/admin/src/content-manager/components/SelectOne/SingleValue.js +0 -67
  158. package/admin/src/content-manager/components/SelectOne/index.js +0 -97
  159. package/admin/src/content-manager/components/SelectWrapper/Label.js +0 -60
  160. package/admin/src/content-manager/components/SelectWrapper/index.js +0 -356
  161. package/admin/src/content-manager/pages/EditSettingsView/components/RelationalFieldButton.js +0 -135
  162. package/admin/src/content-manager/pages/EditSettingsView/components/RelationalFields.js +0 -103
  163. package/admin/src/content-manager/pages/EditView/Header/utils/getDraftRelations.js +0 -62
  164. package/admin/src/contexts/ApiTokenPermissions/index.js +0 -24
  165. package/admin/src/core/apis/CustomFields.js +0 -80
  166. package/admin/src/hooks/useRegenerate/index.js +0 -34
  167. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/ActionBoundRoutes/index.js +0 -56
  168. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/BoundRoute/getMethodColor.js +0 -41
  169. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/BoundRoute/index.js +0 -72
  170. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/CollapsableContentType/CheckBoxWrapper.js +0 -30
  171. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/CollapsableContentType/index.js +0 -150
  172. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/ContenTypesSection/index.js +0 -37
  173. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/FormApiTokenContainer/index.js +0 -254
  174. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/FormBody/index.js +0 -77
  175. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/FormHead/index.js +0 -85
  176. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/Permissions/index.js +0 -40
  177. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/Regenerate/index.js +0 -68
  178. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/init.js +0 -13
  179. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/reducer.js +0 -72
  180. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/utils/getDateOfExpiration.js +0 -16
  181. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/utils/index.js +0 -5
  182. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/utils/transformPermissionsData.js +0 -36
  183. package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/DefaultButton/index.js +0 -63
  184. package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/ReadButton/index.js +0 -19
  185. package/build/1669.d1b29c28.chunk.js +0 -1
  186. package/build/4318.7d167b58.chunk.js +0 -30
  187. package/build/524.40377968.chunk.js +0 -644
  188. package/build/7379.d246dd38.chunk.js +0 -1
  189. package/build/admin-app.a61d5c2e.chunk.js +0 -112
  190. package/build/admin-edit-roles-page.4dd6bcb9.chunk.js +0 -1
  191. package/build/api-tokens-create-page.93dd0689.chunk.js +0 -1
  192. package/build/api-tokens-edit-page.b0adac81.chunk.js +0 -1
  193. package/build/api-tokens-list-page.bb36535f.chunk.js +0 -16
  194. package/build/content-manager.feb0d540.chunk.js +0 -1178
  195. package/build/content-type-builder-list-view.5b3cd768.chunk.js +0 -194
  196. package/build/content-type-builder-translation-en-json.f985c9c4.chunk.js +0 -1
  197. package/build/content-type-builder.a684b2e8.chunk.js +0 -145
  198. package/build/en-json.a9918c93.chunk.js +0 -1
  199. package/build/main.e4065f58.js +0 -9337
  200. package/build/sso-settings-page.9ceb0140.chunk.js +0 -1
  201. package/server/content-types/api-token-permission.js +0 -36
  202. package/server/controllers/content-api.js +0 -15
  203. package/server/routes/content-api.js +0 -20
@@ -1,36 +1,107 @@
1
1
  'use strict';
2
2
 
3
- const { curry, isArray, isEmpty, difference } = require('lodash/fp');
4
- const permissions = require('@strapi/permissions');
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
- module.exports = (params) => {
10
- const { providers } = params;
68
+ return abilityCreator(permissions, options);
69
+ },
11
70
 
12
- const engine = permissions.engine
13
- .new({ providers })
14
71
  /**
15
- * Validate the permission's action exists in the action registry
72
+ * Create an ability factory for a specific user
73
+ * @param user
74
+ * @returns {function(*, *): Promise<Ability>}
16
75
  */
17
- .on('before-format::validate.permission', ({ permission }) => {
18
- const action = providers.action.get(permission.action);
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
- strapi.log.debug(
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
- * Ignore the permission if the fields property is an empty array (access to no field)
130
+ * Update the permission components through various processing
131
+ * @param {Permission} permission
132
+ * @returns {Promise<void>}
53
133
  */
54
- .on('after-format::validate.permission', ({ permission }) => {
55
- const { fields } = permission.properties;
134
+ async applyPermissionProcessors(permission) {
135
+ const context = createWillEvaluateContext(permission);
56
136
 
57
- if (isArray(fields) && isEmpty(fields)) {
58
- return false;
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
- return {
63
- get hooks() {
64
- return engine.hooks;
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
- * Generate an ability based on the given user (using associated roles & permissions)
69
- * @param user
70
- * @returns {Promise<Ability>}
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
- async generateUserAbility(user) {
73
- const permissions = await getService('permission').findUserPermissions(user);
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 engine.generateAbility(permissions, user);
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, ability } = auth;
45
+ const { credentials: apiToken } = auth;
77
46
 
78
47
  if (!apiToken) {
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
- }
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
- // 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);
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
- // 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
- }
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);