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