kuzzle 2.16.11 → 2.17.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 (164) hide show
  1. package/lib/api/controllers/adminController.js +3 -3
  2. package/lib/api/controllers/authController.js +11 -11
  3. package/lib/api/controllers/baseController.js +60 -3
  4. package/lib/api/controllers/clusterController.js +1 -1
  5. package/lib/api/controllers/collectionController.js +7 -5
  6. package/lib/api/controllers/documentController.js +130 -17
  7. package/lib/api/controllers/indexController.js +1 -1
  8. package/lib/api/controllers/memoryStorageController.js +39 -38
  9. package/lib/api/controllers/realtimeController.js +1 -1
  10. package/lib/api/controllers/securityController.js +49 -49
  11. package/lib/api/controllers/serverController.js +73 -27
  12. package/lib/api/documentExtractor.js +3 -3
  13. package/lib/api/funnel.js +40 -21
  14. package/lib/api/httpRoutes.js +9 -4
  15. package/lib/api/openapi/OpenApiManager.d.ts +11 -0
  16. package/lib/api/openapi/OpenApiManager.js +96 -0
  17. package/lib/api/openapi/{document → components/document}/count.yaml +2 -2
  18. package/lib/api/openapi/{document → components/document}/create.yaml +2 -2
  19. package/lib/api/openapi/{document → components/document}/createOrReplace.yaml +2 -2
  20. package/lib/api/openapi/{document → components/document}/delete.yaml +1 -1
  21. package/lib/api/openapi/{document → components/document}/deleteByQuery.yaml +2 -2
  22. package/lib/api/openapi/{document → components/document}/exists.yaml +1 -1
  23. package/lib/api/openapi/{document → components/document}/get.yaml +1 -1
  24. package/lib/api/openapi/{document → components/document}/index.d.ts +2 -0
  25. package/lib/api/openapi/{document → components/document}/index.js +7 -2
  26. package/lib/api/openapi/{document → components/document}/replace.yaml +2 -2
  27. package/lib/api/openapi/{document → components/document}/scroll.yaml +1 -1
  28. package/lib/api/openapi/{document → components/document}/update.yaml +2 -2
  29. package/lib/api/openapi/components/document/validate.yaml +42 -0
  30. package/lib/api/openapi/components/index.d.ts +2 -0
  31. package/lib/api/openapi/components/index.js +18 -0
  32. package/lib/api/openapi/{payloads.yaml → components/payloads.yaml} +0 -0
  33. package/lib/api/openapi/index.d.ts +1 -2
  34. package/lib/api/openapi/index.js +1 -5
  35. package/lib/api/openapi/openApiGenerator.d.ts +7 -0
  36. package/lib/api/openapi/openApiGenerator.js +133 -0
  37. package/lib/api/request/kuzzleRequest.js +4 -0
  38. package/lib/cluster/node.js +9 -9
  39. package/lib/cluster/publisher.js +1 -1
  40. package/lib/cluster/subscriber.js +1 -1
  41. package/lib/cluster/workers/IDCardRenewer.js +2 -2
  42. package/lib/config/default.config.js +1 -0
  43. package/lib/config/index.js +6 -6
  44. package/lib/core/auth/passportResponse.js +6 -6
  45. package/lib/core/auth/passportWrapper.js +5 -5
  46. package/lib/core/backend/backend.d.ts +5 -1
  47. package/lib/core/backend/backend.js +12 -8
  48. package/lib/core/backend/backendConfig.d.ts +5 -1
  49. package/lib/core/backend/backendConfig.js +4 -0
  50. package/lib/core/backend/backendOpenApi.d.ts +9 -0
  51. package/lib/core/backend/backendOpenApi.js +69 -0
  52. package/lib/core/backend/index.d.ts +1 -0
  53. package/lib/core/backend/index.js +1 -0
  54. package/lib/core/network/accessLogger.js +6 -6
  55. package/lib/core/network/clientConnection.js +1 -1
  56. package/lib/core/network/entryPoint.js +5 -5
  57. package/lib/core/network/httpRouter/index.js +5 -5
  58. package/lib/core/network/httpRouter/routeHandler.js +3 -3
  59. package/lib/core/network/httpRouter/routePart.js +5 -5
  60. package/lib/core/network/protocolManifest.js +1 -1
  61. package/lib/core/network/protocols/httpMessage.js +2 -2
  62. package/lib/core/network/protocols/httpwsProtocol.js +205 -48
  63. package/lib/core/network/protocols/mqttProtocol.js +3 -3
  64. package/lib/core/network/protocols/protocol.js +3 -3
  65. package/lib/core/network/router.js +7 -6
  66. package/lib/core/plugin/plugin.js +38 -64
  67. package/lib/core/plugin/pluginManifest.js +3 -3
  68. package/lib/core/plugin/pluginRepository.js +5 -5
  69. package/lib/core/plugin/pluginsManager.js +29 -28
  70. package/lib/core/realtime/notification/server.js +1 -1
  71. package/lib/core/realtime/notification/user.js +1 -1
  72. package/lib/core/realtime/notifier.js +5 -5
  73. package/lib/core/security/index.js +1 -1
  74. package/lib/core/security/profileRepository.d.ts +176 -0
  75. package/lib/core/security/profileRepository.js +426 -443
  76. package/lib/core/security/roleRepository.js +16 -16
  77. package/lib/core/security/securityLoader.js +2 -2
  78. package/lib/core/security/tokenRepository.js +11 -11
  79. package/lib/core/security/userRepository.js +8 -8
  80. package/lib/core/shared/abstractManifest.js +4 -4
  81. package/lib/core/shared/repository.js +5 -5
  82. package/lib/core/shared/sdk/funnelProtocol.js +1 -1
  83. package/lib/core/shared/sdk/impersonatedSdk.js +1 -1
  84. package/lib/core/shared/store.js +30 -23
  85. package/lib/core/statistics/statistics.js +17 -17
  86. package/lib/core/storage/clientAdapter.js +45 -10
  87. package/lib/core/validation/baseType.js +5 -5
  88. package/lib/core/validation/types/anything.js +1 -1
  89. package/lib/core/validation/types/boolean.js +2 -2
  90. package/lib/core/validation/types/date.js +9 -9
  91. package/lib/core/validation/types/email.js +5 -5
  92. package/lib/core/validation/types/enum.js +6 -6
  93. package/lib/core/validation/types/geoPoint.js +2 -2
  94. package/lib/core/validation/types/geoShape.js +28 -25
  95. package/lib/core/validation/types/integer.js +4 -4
  96. package/lib/core/validation/types/ipAddress.js +7 -6
  97. package/lib/core/validation/types/numeric.js +4 -4
  98. package/lib/core/validation/types/object.js +5 -5
  99. package/lib/core/validation/types/string.js +5 -5
  100. package/lib/core/validation/types/url.js +7 -6
  101. package/lib/core/validation/validation.js +95 -84
  102. package/lib/kerror/codes/1-services.json +12 -0
  103. package/lib/kerror/codes/2-api.json +12 -0
  104. package/lib/kerror/codes/3-network.json +12 -0
  105. package/lib/kerror/codes/4-plugin.json +6 -0
  106. package/lib/kerror/codes/index.js +11 -11
  107. package/lib/kerror/index.js +1 -1
  108. package/lib/kuzzle/dumpGenerator.js +3 -3
  109. package/lib/kuzzle/event/kuzzleEventEmitter.js +4 -4
  110. package/lib/kuzzle/event/pipeRunner.js +1 -1
  111. package/lib/kuzzle/event/waterfall.js +6 -6
  112. package/lib/kuzzle/kuzzle.js +36 -5
  113. package/lib/kuzzle/log.js +3 -3
  114. package/lib/kuzzle/vault.js +3 -3
  115. package/lib/model/security/profile.d.ts +54 -0
  116. package/lib/model/security/profile.js +174 -233
  117. package/lib/model/security/rights.js +1 -1
  118. package/lib/model/security/role.d.ts +40 -0
  119. package/lib/model/security/role.js +159 -191
  120. package/lib/model/security/user.d.ts +29 -0
  121. package/lib/model/security/user.js +84 -52
  122. package/lib/model/storage/apiKey.js +2 -2
  123. package/lib/model/storage/baseModel.js +3 -3
  124. package/lib/service/cache/redis.js +7 -7
  125. package/lib/service/storage/elasticsearch.js +152 -90
  126. package/lib/service/storage/esWrapper.js +2 -3
  127. package/lib/types/ControllerDefinition.d.ts +3 -3
  128. package/lib/types/ControllerRights.d.ts +22 -0
  129. package/lib/types/ControllerRights.js +23 -0
  130. package/lib/types/HttpStream.d.ts +32 -0
  131. package/lib/types/HttpStream.js +70 -0
  132. package/lib/types/OpenApiDefinition.d.ts +43 -0
  133. package/lib/types/{config/StorageService/StorageServiceElasticsearchConfiguration.js → OpenApiDefinition.js} +1 -1
  134. package/lib/types/Policy.d.ts +25 -0
  135. package/lib/types/{InternalLogger.js → Policy.js} +2 -2
  136. package/lib/types/PolicyRestrictions.d.ts +21 -0
  137. package/lib/types/PolicyRestrictions.js +23 -0
  138. package/lib/types/Target.d.ts +15 -0
  139. package/lib/types/Target.js +23 -0
  140. package/lib/types/config/KuzzleConfiguration.d.ts +4 -0
  141. package/lib/types/config/ServicesConfiguration.d.ts +2 -2
  142. package/lib/types/config/{StorageService/StorageServiceElasticsearchConfiguration.d.ts → storageEngine/StorageEngineElasticsearchConfiguration.d.ts} +10 -3
  143. package/lib/types/config/storageEngine/StorageEngineElasticsearchConfiguration.js +3 -0
  144. package/lib/types/index.d.ts +7 -1
  145. package/lib/types/index.js +7 -1
  146. package/lib/util/array.d.ts +11 -0
  147. package/lib/util/array.js +57 -0
  148. package/lib/util/assertType.js +6 -6
  149. package/lib/util/bufferedPassThrough.d.ts +76 -0
  150. package/lib/util/bufferedPassThrough.js +161 -0
  151. package/lib/util/deprecate.js +7 -5
  152. package/lib/util/didYouMean.js +1 -1
  153. package/lib/util/dump-collection.d.ts +3 -0
  154. package/lib/util/dump-collection.js +265 -0
  155. package/lib/util/extractFields.js +2 -2
  156. package/lib/util/inflector.d.ts +8 -0
  157. package/lib/util/inflector.js +16 -0
  158. package/lib/util/requestAssertions.js +7 -7
  159. package/lib/util/wildcard.js +55 -0
  160. package/package-lock.json +538 -78
  161. package/package.json +5 -3
  162. package/lib/api/openApiGenerator.d.ts +0 -7
  163. package/lib/api/openApiGenerator.js +0 -197
  164. package/lib/types/InternalLogger.d.ts +0 -25
@@ -1,3 +1,4 @@
1
+ "use strict";
1
2
  /*
2
3
  * Kuzzle, a backend software, self-hostable and ready to use
3
4
  * to power modern apps
@@ -18,487 +19,469 @@
18
19
  * See the License for the specific language governing permissions and
19
20
  * limitations under the License.
20
21
  */
21
-
22
- 'use strict';
23
-
24
- const { omit } = require('lodash');
25
- const Bluebird = require('bluebird');
26
-
27
- const Profile = require('../../model/security/profile');
28
- const Repository = require('../shared/repository');
29
- const kerror = require('../../kerror');
30
- const cacheDbEnum = require('../cache/cacheDbEnum');
31
-
22
+ var __importDefault = (this && this.__importDefault) || function (mod) {
23
+ return (mod && mod.__esModule) ? mod : { "default": mod };
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.ProfileRepository = void 0;
27
+ const lodash_1 = require("lodash");
28
+ const bluebird_1 = __importDefault(require("bluebird"));
29
+ const profile_1 = require("../../model/security/profile");
30
+ const repository_1 = __importDefault(require("../shared/repository"));
31
+ const kerror_1 = __importDefault(require("../../kerror"));
32
+ const cacheDbEnum_1 = __importDefault(require("../cache/cacheDbEnum"));
32
33
  /**
33
34
  * @class ProfileRepository
34
35
  * @extends Repository
35
36
  */
36
- class ProfileRepository extends Repository {
37
- /**
38
- * @constructor
39
- */
40
- constructor (securityModule) {
41
- super({
42
- cache: cacheDbEnum.NONE,
43
- store: global.kuzzle.internalIndex,
44
- });
45
-
46
- this.module = securityModule;
47
- this.collection = 'profiles';
48
- this.ObjectConstructor = Profile;
49
- this.profiles = new Map();
50
- }
51
-
52
- init () {
37
+ class ProfileRepository extends repository_1.default {
53
38
  /**
54
- * Creates a new profile
55
- * @param {String} id - profile identifier / name
56
- * @param {Object} policies
57
- * @param {Object} opts - refresh, userId (used for metadata)
58
- * @returns {Profile}
59
- * @throws If already exists or if the policies are invalid
39
+ * @constructor
60
40
  */
61
- global.kuzzle.onAsk(
62
- 'core:security:profile:create',
63
- (id, policies, opts) => this.create(id, policies, opts));
64
-
41
+ constructor(securityModule) {
42
+ super({
43
+ cache: cacheDbEnum_1.default.NONE,
44
+ store: global.kuzzle.internalIndex,
45
+ });
46
+ this.module = securityModule;
47
+ this.profiles = new Map();
48
+ super.collection = 'profiles';
49
+ super.ObjectConstructor = profile_1.Profile;
50
+ }
51
+ init() {
52
+ /**
53
+ * Creates a new profile
54
+ * @param {String} id - profile identifier / name
55
+ * @param {Object} policies
56
+ * @param {Object} opts - refresh, userId (used for metadata)
57
+ * @returns {Profile}
58
+ * @throws If already exists or if the policies are invalid
59
+ */
60
+ global.kuzzle.onAsk('core:security:profile:create', (id, policies, opts) => this.create(id, policies, opts));
61
+ /**
62
+ * Creates a new profile, or replaces it if it already exists
63
+ * @param {String} id
64
+ * @param {Object} policies
65
+ * @param {Object} opts - refresh, userId (used for metadata)
66
+ * @returns {Profile}
67
+ * @throws If the profile policies are invalid
68
+ */
69
+ global.kuzzle.onAsk('core:security:profile:createOrReplace', (id, policies, opts) => this.createOrReplace(id, policies, opts));
70
+ /**
71
+ * Deletes an existing profile
72
+ * @param {String} id
73
+ * @param {Object} opts - refresh
74
+ * @throws If the profile doesn't exist, if it is protected, or if it's
75
+ * still in use
76
+ */
77
+ global.kuzzle.onAsk('core:security:profile:delete', (id, opts) => this.deleteById(id, opts));
78
+ /**
79
+ * Loads and returns an existing profile
80
+ * @param {String} id - profile identifier
81
+ * @returns {Profile}
82
+ * @throws {NotFoundError} If the profile doesn't exist
83
+ */
84
+ global.kuzzle.onAsk('core:security:profile:get', id => this.load(id));
85
+ /**
86
+ * Invalidates the RAM cache from the given profile ID. If none is provided,
87
+ * the entire cache is emptied.
88
+ *
89
+ * @param {String} [id] - profile identifier
90
+ */
91
+ global.kuzzle.onAsk('core:security:profile:invalidate', id => this.invalidate(id));
92
+ /**
93
+ * Gets multiple profiles
94
+ * @param {Array} ids
95
+ * @returns {Array.<Profile>}
96
+ * @throws If one or more profiles don't exist
97
+ */
98
+ global.kuzzle.onAsk('core:security:profile:mGet', ids => this.loadProfiles(ids));
99
+ /**
100
+ * Fetches the next page of search results
101
+ * @param {String} id - scroll identifier
102
+ * @param {String} [ttl] - refresh the scroll results TTL
103
+ * @returns {Object} Search results
104
+ */
105
+ global.kuzzle.onAsk('core:security:profile:scroll', (id, ttl) => this.scroll(id, ttl));
106
+ /**
107
+ * Searches profiles
108
+ *
109
+ * @param {Object} searchBody - Search query (ES format)
110
+ * @param {Object} opts (from, size, scroll)
111
+ *
112
+ * @returns {Object} Search results
113
+ */
114
+ global.kuzzle.onAsk('core:security:profile:search', (searchBody, opts) => this.search(searchBody, opts));
115
+ /**
116
+ * Removes all existing profiles and invalidates the RAM cache
117
+ * @param {Object} opts (refresh)
118
+ */
119
+ global.kuzzle.onAsk('core:security:profile:truncate', opts => this.truncate(opts));
120
+ /**
121
+ * Updates an existing profile using a partial content
122
+ * @param {String} id - profile identifier to update
123
+ * @param {Object} policies - partial policies to apply
124
+ * @param {Object} opts - refresh, retryOnConflict, userId (used for metadata)
125
+ * @returns {Profile} Updated profile
126
+ */
127
+ global.kuzzle.onAsk('core:security:profile:update', (id, content, opts) => this.update(id, content, opts));
128
+ }
65
129
  /**
66
- * Creates a new profile, or replaces it if it already exists
67
- * @param {String} id
68
- * @param {Object} policies
69
- * @param {Object} opts - refresh, userId (used for metadata)
70
- * @returns {Profile}
71
- * @throws If the profile policies are invalid
130
+ * Loads a Profile
131
+ *
132
+ * @param {string} id
133
+ * @returns {Promise.<Promise>}
134
+ * @throws {NotFoundError} If the corresponding profile doesn't exist
72
135
  */
73
- global.kuzzle.onAsk(
74
- 'core:security:profile:createOrReplace',
75
- (id, policies, opts) => this.createOrReplace(id, policies, opts));
76
-
136
+ async load(id) {
137
+ if (this.profiles.has(id)) {
138
+ return this.profiles.get(id);
139
+ }
140
+ const profile = await super.load(id);
141
+ profile.optimizedPolicies = this.optimizePolicies(profile.policies);
142
+ this.profiles.set(id, profile);
143
+ return profile;
144
+ }
77
145
  /**
78
- * Deletes an existing profile
79
- * @param {String} id
80
- * @param {Object} opts - refresh
81
- * @throws If the profile doesn't exist, if it is protected, or if it's
82
- * still in use
146
+ * Loads a Profile object given its id.
147
+ * Stores the promise of the profile being loaded in the memcache
148
+ * and then replaces it by the profile itself once it has been loaded
149
+ *
150
+ * This is to allow parallelisation while preventing sending requests
151
+ * to ES, which is slow
152
+ *
153
+ * @param {Array} profileIds - Array of profiles ids
154
+ * @param {Object} options - resetCache (false)
155
+ *
156
+ * @returns {Promise} Resolves to the matching Profile object if found, null
157
+ * if not.
158
+ */
159
+ async loadProfiles(profileIds = []) {
160
+ const profiles = [];
161
+ if (profileIds.some(p => typeof p !== 'string')) {
162
+ throw kerror_1.default.get('api', 'assert', 'invalid_type', 'profileIds', 'string[]');
163
+ }
164
+ for (const id of profileIds) {
165
+ let profile = this.profiles.get(id);
166
+ if (!profile) {
167
+ profile = this.loadOneFromDatabase(id)
168
+ .then(p => {
169
+ p.optimizedPolicies = this.optimizePolicies(p.policies);
170
+ this.profiles.set(id, p);
171
+ return p;
172
+ });
173
+ }
174
+ profiles.push(profile);
175
+ }
176
+ return bluebird_1.default.all(profiles);
177
+ }
178
+ /**
179
+ * @override
83
180
  */
84
- global.kuzzle.onAsk(
85
- 'core:security:profile:delete',
86
- (id, opts) => this.deleteById(id, opts));
87
-
181
+ async loadOneFromDatabase(id) {
182
+ try {
183
+ return await super.loadOneFromDatabase(id);
184
+ }
185
+ catch (err) {
186
+ if (err.status === 404) {
187
+ throw kerror_1.default.get('security', 'profile', 'not_found', id);
188
+ }
189
+ throw err;
190
+ }
191
+ }
88
192
  /**
89
- * Loads and returns an existing profile
90
- * @param {String} id - profile identifier
193
+ * Creates a new profile, or create/replace a profile
194
+ *
195
+ * @param {String} id
196
+ * @param {Object} policies
197
+ * @param {Object} [opts]
91
198
  * @returns {Profile}
92
- * @throws {NotFoundError} If the profile doesn't exist
93
199
  */
94
- global.kuzzle.onAsk('core:security:profile:get', id => this.load(id));
95
-
200
+ async _createOrReplace(id, content, { method, refresh = 'false', strict, userId = null } = {}) {
201
+ const profile = await this.fromDTO({
202
+ // content should be first: ignores _id and _kuzzle_info in it
203
+ ...content,
204
+ _id: id,
205
+ _kuzzle_info: {
206
+ author: userId,
207
+ createdAt: Date.now(),
208
+ updatedAt: null,
209
+ updater: null,
210
+ },
211
+ });
212
+ return this.validateAndSaveProfile(profile, { method, refresh, strict });
213
+ }
96
214
  /**
97
- * Invalidates the RAM cache from the given profile ID. If none is provided,
98
- * the entire cache is emptied.
215
+ * Creates a new profile
99
216
  *
100
- * @param {String} [id] - profile identifier
217
+ * @param {String} id
218
+ * @param {Object} content
219
+ * @param {Object} [opts]
220
+ * @returns {Profile}
101
221
  */
102
- global.kuzzle.onAsk(
103
- 'core:security:profile:invalidate',
104
- id => this.invalidate(id));
105
-
222
+ async create(id, content, opts = {}) {
223
+ return this._createOrReplace(id, content, {
224
+ method: 'create',
225
+ ...opts,
226
+ });
227
+ }
106
228
  /**
107
- * Gets multiple profiles
108
- * @param {Array} ids
109
- * @returns {Array.<Profile>}
110
- * @throws If one or more profiles don't exist
229
+ * Creates or replaces a profile
230
+ *
231
+ * @param {String} id
232
+ * @param {Object} content
233
+ * @param {Object} [opts]
234
+ * @returns {Profile}
111
235
  */
112
- global.kuzzle.onAsk(
113
- 'core:security:profile:mGet',
114
- ids => this.loadProfiles(ids));
115
-
236
+ async createOrReplace(id, content, opts = {}) {
237
+ return this._createOrReplace(id, content, {
238
+ method: 'createOrReplace',
239
+ ...opts,
240
+ });
241
+ }
116
242
  /**
117
- * Fetches the next page of search results
118
- * @param {String} id - scroll identifier
119
- * @param {String} [ttl] - refresh the scroll results TTL
120
- * @returns {Object} Search results
243
+ * Updates a profile
244
+ * @param {String} id
245
+ * @param {Object} content
246
+ * @param {Object} [opts]
247
+ * @returns {Promise}
121
248
  */
122
- global.kuzzle.onAsk(
123
- 'core:security:profile:scroll',
124
- (id, ttl) => this.scroll(id, ttl));
125
-
249
+ async update(id, content, { refresh, retryOnConflict, strict, userId } = {}) {
250
+ const profile = await this.load(id);
251
+ const pojo = super.toDTO(profile);
252
+ const updated = await this.fromDTO({
253
+ // /!\ order is important
254
+ ...pojo,
255
+ ...content,
256
+ // Always last, in case content contains these keys
257
+ _id: id,
258
+ _kuzzle_info: {
259
+ updatedAt: Date.now(),
260
+ updater: userId,
261
+ },
262
+ });
263
+ return this.validateAndSaveProfile(updated, {
264
+ method: 'update',
265
+ refresh,
266
+ retryOnConflict,
267
+ strict,
268
+ });
269
+ }
126
270
  /**
127
- * Searches profiles
128
- *
129
- * @param {Object} searchBody - Search query (ES format)
130
- * @param {Object} opts (from, size, scroll)
271
+ * Deletes a profile
131
272
  *
132
- * @returns {Object} Search results
273
+ * @param {String} id
274
+ * @param {object} [options]
275
+ * @returns {Promise}
133
276
  */
134
- global.kuzzle.onAsk(
135
- 'core:security:profile:search',
136
- (searchBody, opts) => this.search(searchBody, opts));
137
-
277
+ async deleteById(id, options = {}) {
278
+ const profile = await this.load(id);
279
+ return this.delete(profile, options);
280
+ }
138
281
  /**
139
- * Removes all existing profiles and invalidates the RAM cache
140
- * @param {Object} opts (refresh)
282
+ * @override
141
283
  */
142
- global.kuzzle.onAsk(
143
- 'core:security:profile:truncate',
144
- opts => this.truncate(opts));
145
-
284
+ async delete(profile, { refresh = 'false', onAssignedUsers = 'fail', userId = '-1', } = {}) {
285
+ if (['admin', 'default', 'anonymous'].includes(profile._id)) {
286
+ throw kerror_1.default.get('security', 'profile', 'cannot_delete');
287
+ }
288
+ const query = {
289
+ terms: {
290
+ 'profileIds': [profile._id]
291
+ }
292
+ };
293
+ if (onAssignedUsers === 'remove') {
294
+ const batch = [];
295
+ let treated = 0;
296
+ let userPage = await this.module.user.search({ query }, { scroll: '1m', size: 100 });
297
+ while (treated < userPage.total) {
298
+ batch.length = 0;
299
+ for (const user of userPage.hits) {
300
+ user.profileIds = user.profileIds.filter(e => e !== profile._id);
301
+ if (user.profileIds.length === 0) {
302
+ user.profileIds.push('anonymous');
303
+ }
304
+ batch.push(this.module.user.update(user._id, user.profileIds, user, {
305
+ refresh,
306
+ userId
307
+ }));
308
+ }
309
+ await bluebird_1.default.all(batch);
310
+ treated += userPage.hits.length;
311
+ if (treated < userPage.total) {
312
+ userPage = await this.module.user.scroll(userPage.scrollId, '1m');
313
+ }
314
+ }
315
+ }
316
+ else {
317
+ const hits = await this.module.user.search({ query }, { from: 0, size: 1 });
318
+ if (hits.total > 0) {
319
+ throw kerror_1.default.get('security', 'profile', 'in_use');
320
+ }
321
+ }
322
+ await this.deleteFromDatabase(profile._id, { refresh });
323
+ this.profiles.delete(profile._id);
324
+ }
146
325
  /**
147
- * Updates an existing profile using a partial content
148
- * @param {String} id - profile identifier to update
149
- * @param {Object} policies - partial policies to apply
150
- * @param {Object} opts - refresh, retryOnConflict, userId (used for metadata)
151
- * @returns {Profile} Updated profile
326
+ * From a Profile object, returns a serialized object ready to be persisted
327
+ * to the database.
328
+ *
329
+ * @param {Profile} profile
330
+ * @returns {object}
152
331
  */
153
- global.kuzzle.onAsk(
154
- 'core:security:profile:update',
155
- (id, content, opts) => this.update(id, content, opts));
156
- }
157
-
158
- /**
159
- * Loads a Profile
160
- *
161
- * @param {string} id
162
- * @returns {Promise.<Promise>}
163
- * @throws {NotFoundError} If the corresponding profile doesn't exist
164
- */
165
- async load (id) {
166
- if (this.profiles.has(id)) {
167
- return this.profiles.get(id);
168
- }
169
-
170
- const profile = await super.load(id);
171
-
172
- this.profiles.set(id, profile);
173
-
174
- return profile;
175
- }
176
-
177
- /**
178
- * Loads a Profile object given its id.
179
- * Stores the promise of the profile being loaded in the memcache
180
- * and then replaces it by the profile itself once it has been loaded
181
- *
182
- * This is to allow parallelisation while preventing sending requests
183
- * to ES, which is slow
184
- *
185
- * @param {Array} profileIds - Array of profiles ids
186
- * @param {Object} options - resetCache (false)
187
- *
188
- * @returns {Promise} Resolves to the matching Profile object if found, null
189
- * if not.
190
- */
191
- async loadProfiles (profileIds = []) {
192
- const profiles = [];
193
-
194
- if (profileIds.some(p => typeof p !== 'string')) {
195
- throw kerror.get('api', 'assert', 'invalid_type', 'profileIds', 'string[]');
196
- }
197
-
198
- for (let i = 0; i < profileIds.length; i++) {
199
- const id = profileIds[i];
200
- let profile = this.profiles.get(id);
201
-
202
- if (!profile) {
203
- profile = this.loadOneFromDatabase(id)
204
- .then(p => {
205
- this.profiles.set(id, p);
206
- return p;
207
- });
208
-
209
- this.profiles.set(id, profile);
210
- }
211
-
212
- profiles.push(profile);
213
- }
214
-
215
- return Bluebird.all(profiles);
216
- }
217
-
218
- /**
219
- * @override
220
- */
221
- async loadOneFromDatabase (id) {
222
- try {
223
- return await super.loadOneFromDatabase(id);
224
- }
225
- catch(err) {
226
- if (err.status === 404) {
227
- throw kerror.get('security', 'profile', 'not_found', id);
228
- }
229
- throw err;
332
+ serializeToDatabase(profile) {
333
+ // avoid the profile var mutation
334
+ return (0, lodash_1.omit)(profile, ['_id']);
230
335
  }
231
- }
232
-
233
- /**
234
- * Creates a new profile, or create/replace a profile
235
- *
236
- * @param {String} id
237
- * @param {Object} policies
238
- * @param {Object} [opts]
239
- * @returns {Profile}
240
- */
241
- async _createOrReplace (
242
- id,
243
- content,
244
- { method, refresh = 'false', strict, userId = null } = {}
245
- ) {
246
- const profile = await this.fromDTO({
247
- // content should be first: ignores _id and _kuzzle_info in it
248
- ...content,
249
- _id: id,
250
- _kuzzle_info: {
251
- author: userId,
252
- createdAt: Date.now(),
253
- updatedAt: null,
254
- updater: null,
255
- },
256
- });
257
-
258
- return this.validateAndSaveProfile(profile, { method, refresh, strict });
259
- }
260
-
261
- /**
262
- * Creates a new profile
263
- *
264
- * @param {String} id
265
- * @param {Object} content
266
- * @param {Object} [opts]
267
- * @returns {Profile}
268
- */
269
- async create (id, content, opts) {
270
- return this._createOrReplace(id, content, {
271
- method: 'create',
272
- ...opts,
273
- });
274
- }
275
-
276
- /**
277
- * Creates or replaces a profile
278
- *
279
- * @param {String} id
280
- * @param {Object} content
281
- * @param {Object} [opts]
282
- * @returns {Profile}
283
- */
284
- async createOrReplace (id, content, opts) {
285
- return this._createOrReplace(id, content, {
286
- method: 'createOrReplace',
287
- ...opts,
288
- });
289
- }
290
-
291
- /**
292
- * Updates a profile
293
- * @param {String} id
294
- * @param {Object} content
295
- * @param {Object} [opts]
296
- * @returns {Promise}
297
- */
298
- async update (id, content, {refresh, retryOnConflict, strict, userId} = {}) {
299
- const profile = await this.load(id);
300
- const pojo = this.toDTO(profile);
301
- const updated = await this.fromDTO({
302
- // /!\ order is important
303
- ...pojo,
304
- ...content,
305
- // Always last, in case content contains these keys
306
- _id: id,
307
- _kuzzle_info: {
308
- updatedAt: Date.now(),
309
- updater: userId,
310
- },
311
- });
312
-
313
- return this.validateAndSaveProfile(updated, {
314
- method: 'update',
315
- refresh,
316
- retryOnConflict,
317
- strict,
318
- });
319
- }
320
-
321
- /**
322
- * Deletes a profile
323
- *
324
- * @param {String} id
325
- * @param {object} [options]
326
- * @returns {Promise}
327
- */
328
- async deleteById (id, options) {
329
- const profile = await this.load(id);
330
- return this.delete(profile, options);
331
- }
332
-
333
- /**
334
- * @override
335
- */
336
- async delete (profile, {
337
- refresh = 'false',
338
- onAssignedUsers = 'fail',
339
- userId = '-1',
340
- } = {}) {
341
- if (['admin', 'default', 'anonymous'].includes(profile._id)) {
342
- throw kerror.get('security', 'profile', 'cannot_delete');
336
+ /**
337
+ * Given a Profile object, validates its definition and if OK, persist it to the database.
338
+ *
339
+ * @param {Profile} profile
340
+ * @param {Object} [options]
341
+ * @param {string} [options.method] - Document persistence method
342
+ * @param {string} [options.refresh] - (Don't) wait for index refresh
343
+ * @param {number} [options.retryOnConflict] - Number of retries when an
344
+ * update fails due to a conflict
345
+ * @param {boolean} [options.strict] - if true, restrictions can only be
346
+ * applied on existing indexes/collections
347
+ * @returns {Promise<Profile>}
348
+ **/
349
+ async validateAndSaveProfile(profile, { method, refresh, retryOnConflict, strict } = {}) {
350
+ const policiesRoles = profile.policies.map(p => p.roleId);
351
+ // Assert: all roles must exist
352
+ await this.module.role.loadRoles(policiesRoles);
353
+ await profile.validateDefinition({ strict });
354
+ if (profile._id === 'anonymous'
355
+ && policiesRoles.indexOf('anonymous') === -1) {
356
+ throw kerror_1.default.get('security', 'profile', 'missing_anonymous_role');
357
+ }
358
+ profile.optimizedPolicies = undefined; // Remove optimized policies
359
+ await super.persistToDatabase(profile, { method, refresh, retryOnConflict });
360
+ const updatedProfile = await this.loadOneFromDatabase(profile._id);
361
+ // Recompute optimized policies based on new policies
362
+ updatedProfile.optimizedPolicies = this.optimizePolicies(updatedProfile.policies);
363
+ this.profiles.set(profile._id, updatedProfile);
364
+ return updatedProfile;
343
365
  }
344
-
345
- const query = {
346
- terms: {
347
- 'profileIds': [ profile._id ]
348
- }
349
- };
350
-
351
- if (onAssignedUsers === 'remove') {
352
- const batch = [];
353
- let treated = 0;
354
- let userPage = await this.module.user.search(
355
- { query },
356
- { scroll: '1m', size: 100 });
357
-
358
- while (treated < userPage.total) {
359
- batch.length = 0;
360
-
361
- for (const user of userPage.hits) {
362
- user.profileIds = user.profileIds.filter(e => e !== profile._id);
363
-
364
- if (user.profileIds.length === 0) {
365
- user.profileIds.push('anonymous');
366
- }
367
-
368
- batch.push(this.module.user.update(
369
- user._id,
370
- user.profileIds,
371
- user,
372
- {
373
- refresh,
374
- userId
375
- }));
366
+ /**
367
+ * @param {object} dto
368
+ * @returns {Promise<Profile>}
369
+ */
370
+ async fromDTO(dto) {
371
+ const profile = await super.fromDTO(dto);
372
+ // force "default" role/policy if the profile does not have any role in it
373
+ if (!profile.policies || profile.policies.length === 0) {
374
+ profile.policies = [{ roleId: 'default' }];
376
375
  }
377
-
378
- await Bluebird.all(batch);
379
-
380
- treated += userPage.hits.length;
381
-
382
- if (treated < userPage.total) {
383
- userPage = await this.module.user.scroll(userPage.scrollId, '1m');
376
+ if (profile.constructor._hash('') === false) {
377
+ profile.constructor._hash = obj => global.kuzzle.hash(obj);
384
378
  }
385
- }
386
- }
387
- else {
388
- const hits = await this.module.user.search({ query }, {from: 0, size: 1});
389
-
390
- if (hits.total > 0) {
391
- throw kerror.get('security', 'profile', 'in_use');
392
- }
379
+ const policiesRoles = profile.policies.map(p => p.roleId);
380
+ const roles = await this.module.role.loadRoles(policiesRoles);
381
+ // Fail if not all roles are found
382
+ if (roles.some(r => r === null)) {
383
+ throw kerror_1.default.get('security', 'profile', 'cannot_hydrate');
384
+ }
385
+ return profile;
393
386
  }
394
-
395
- await this.deleteFromDatabase(profile._id, {refresh});
396
-
397
- this.profiles.delete(profile._id);
398
- }
399
-
400
- /**
401
- * From a Profile object, returns a serialized object ready to be persisted
402
- * to the database.
403
- *
404
- * @param {Profile} profile
405
- * @returns {object}
406
- */
407
- serializeToDatabase (profile) {
408
- // avoid the profile var mutation
409
- return omit(profile, ['_id']);
410
- }
411
-
412
- /**
413
- * Given a Profile object, validates its definition and if OK, persist it to the database.
414
- *
415
- * @param {Profile} profile
416
- * @param {Object} [options]
417
- * @param {string} [options.method] - Document persistence method
418
- * @param {string} [options.refresh] - (Don't) wait for index refresh
419
- * @param {number} [options.retryOnConflict] - Number of retries when an
420
- * update fails due to a conflict
421
- * @param {boolean} [options.strict] - if true, restrictions can only be
422
- * applied on existing indexes/collections
423
- * @returns {Promise<Profile>}
424
- **/
425
- async validateAndSaveProfile (profile, { method, refresh, retryOnConflict, strict } = {}) {
426
- const policiesRoles = profile.policies.map(p => p.roleId);
427
-
428
- // Assert: all roles must exist
429
- await this.module.role.loadRoles(policiesRoles);
430
-
431
- await profile.validateDefinition({ strict });
432
-
433
- if ( profile._id === 'anonymous'
434
- && policiesRoles.indexOf('anonymous') === -1
435
- ) {
436
- throw kerror.get('security', 'profile', 'missing_anonymous_role');
387
+ /**
388
+ * @override
389
+ */
390
+ async truncate(opts) {
391
+ try {
392
+ await super.truncate(opts);
393
+ }
394
+ finally {
395
+ // always clear the RAM cache: even if truncate fails in the middle of it,
396
+ // some of the cached profiles might not be valid anymore
397
+ this.invalidate();
398
+ }
437
399
  }
438
-
439
- await this.persistToDatabase(profile, { method, refresh, retryOnConflict });
440
-
441
- const updatedProfile = await this.loadOneFromDatabase(profile._id);
442
-
443
- this.profiles.set(profile._id, updatedProfile);
444
- return updatedProfile;
445
- }
446
-
447
- /**
448
- * @param {object} dto
449
- * @returns {Promise<Profile>}
450
- */
451
- async fromDTO (dto) {
452
- const profile = await super.fromDTO(dto);
453
-
454
- // force "default" role/policy if the profile does not have any role in it
455
- if (!profile.policies || profile.policies.length === 0) {
456
- profile.policies = [ {roleId: 'default'} ];
400
+ /**
401
+ * Invalidate the cache entries for the given profile. If none is provided,
402
+ * the entire cache is emptied.
403
+ * @param {string} [profileId]
404
+ */
405
+ invalidate(profileId) {
406
+ if (!profileId) {
407
+ this.profiles.clear();
408
+ }
409
+ else {
410
+ this.profiles.delete(profileId);
411
+ }
457
412
  }
458
-
459
- if (profile.constructor._hash('') === false) {
460
- profile.constructor._hash = obj => global.kuzzle.hash(obj);
413
+ /**
414
+ * Optimize each policy to get a O(1) index access time
415
+ * and a O(log(n)) collection search time.
416
+ *
417
+ * - Deduplicate indexes using a map
418
+ * - Sort collections per index
419
+ * @param {Object[]} policies
420
+ */
421
+ optimizePolicies(policies) {
422
+ if (!policies) {
423
+ return [];
424
+ }
425
+ return policies.map(this.optimizePolicy);
461
426
  }
462
-
463
- const policiesRoles = profile.policies.map(p => p.roleId);
464
- const roles = await this.module.role.loadRoles(policiesRoles);
465
-
466
- // Fail if not all roles are found
467
- if (roles.some(r => r === null)) {
468
- throw kerror.get('security', 'profile', 'cannot_hydrate');
427
+ /**
428
+ * Optimize a policy to get a O(1) index access time
429
+ * and a O(log(n)) collection search time.
430
+ *
431
+ * - Deduplicate indexes using a map
432
+ * - Sort collections per index
433
+ * @param policy
434
+ */
435
+ optimizePolicy(policy) {
436
+ const indexes = new Map();
437
+ if (!policy.restrictedTo) {
438
+ return {
439
+ roleId: policy.roleId,
440
+ };
441
+ }
442
+ for (const restriction of policy.restrictedTo) {
443
+ const index = restriction.index;
444
+ const collections = restriction.collections;
445
+ if (!index) {
446
+ continue;
447
+ }
448
+ if (!indexes.has(index)) {
449
+ indexes.set(index, new Set());
450
+ }
451
+ if (!collections) {
452
+ continue;
453
+ }
454
+ const collectionSet = indexes.get(index);
455
+ for (const collection of collections) {
456
+ collectionSet.add(collection); // Push unique values
457
+ }
458
+ }
459
+ // Convert collections Set to arrays and sort them
460
+ for (const index of indexes.keys()) {
461
+ const collectionSet = indexes.get(index);
462
+ indexes.set(index, Array.from(collectionSet).sort());
463
+ }
464
+ return {
465
+ restrictedTo: indexes,
466
+ roleId: policy.roleId,
467
+ };
469
468
  }
470
-
471
- return profile;
472
- }
473
-
474
- /**
475
- * @override
476
- */
477
- async truncate (opts) {
478
- try {
479
- await super.truncate(opts);
469
+ // ============================================
470
+ // Every method described after are for testing purpose only
471
+ // Otherwise we cannot stub them
472
+ // ============================================
473
+ async toDTO(dto) {
474
+ return super.toDTO(dto);
480
475
  }
481
- finally {
482
- // always clear the RAM cache: even if truncate fails in the middle of it,
483
- // some of the cached profiles might not be valid anymore
484
- this.invalidate();
476
+ async deleteFromDatabase(id, options) {
477
+ return super.deleteFromDatabase(id, options);
485
478
  }
486
- }
487
-
488
- /**
489
- * Invalidate the cache entries for the given profile. If none is provided,
490
- * the entire cache is emptied.
491
- * @param {string} [profileId]
492
- */
493
- invalidate (profileId) {
494
- if (!profileId) {
495
- this.profiles.clear();
479
+ async search(searchBody, options) {
480
+ return super.search(searchBody, options);
496
481
  }
497
- else {
498
- this.profiles.delete(profileId);
482
+ async scroll(id, ttl) {
483
+ return super.scroll(id, ttl);
499
484
  }
500
- }
501
485
  }
502
-
503
-
504
- module.exports = ProfileRepository;
486
+ exports.ProfileRepository = ProfileRepository;
487
+ //# sourceMappingURL=profileRepository.js.map