kuzzle 2.17.8 → 2.18.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.
@@ -646,7 +646,12 @@ class AuthController extends NativeController {
646
646
 
647
647
  assertIsAuthenticated (request) {
648
648
  if (request.context.user._id === this.anonymousId) {
649
- throw kerror.get('security', 'rights', 'unauthorized');
649
+ throw kerror.get(
650
+ 'security',
651
+ 'rights',
652
+ 'unauthorized',
653
+ request.input.controller,
654
+ request.input.action);
650
655
  }
651
656
  }
652
657
  }
@@ -21,8 +21,6 @@
21
21
 
22
22
  'use strict';
23
23
 
24
- const Bluebird = require('bluebird');
25
-
26
24
  const { Request } = require('../request');
27
25
  const { NativeController } = require('./baseController');
28
26
 
@@ -119,7 +117,7 @@ class IndexController extends NativeController {
119
117
  );
120
118
  }
121
119
 
122
- await Bluebird.all(promises);
120
+ await Promise.all(promises);
123
121
  }
124
122
 
125
123
  return response;
@@ -148,12 +146,18 @@ class IndexController extends NativeController {
148
146
  }
149
147
 
150
148
  /**
151
- * Returns a list of indexes allowed to be deleted by the user
149
+ * Returns a list of indexes allowed to be deleted by the user.
150
+ *
151
+ * Returns entire list of public indexes when called from EmbeddedSDK
152
152
  *
153
153
  * @param {Request} request
154
- * @param {String[]} publicIndexes - Complete indexes list
154
+ * @param {String[]} publicIndexes - Public indexes list
155
155
  */
156
156
  _allowedIndexes (request, publicIndexes) {
157
+ if (request.getUser() === null) {
158
+ return publicIndexes;
159
+ }
160
+
157
161
  const allowedIndexes = [];
158
162
 
159
163
  const promises = publicIndexes
@@ -171,7 +175,7 @@ class IndexController extends NativeController {
171
175
  });
172
176
  });
173
177
 
174
- return Bluebird.all(promises)
178
+ return Promise.all(promises)
175
179
  .then(() => allowedIndexes);
176
180
  }
177
181
  }
@@ -31,7 +31,7 @@ const { NativeController } = require('./baseController');
31
31
  const formatProcessing = require('../../core/auth/formatProcessing');
32
32
  const ApiKey = require('../../model/storage/apiKey');
33
33
  const kerror = require('../../kerror');
34
- const { has, get } = require('../../util/safeObject');
34
+ const { has } = require('../../util/safeObject');
35
35
  const { generateRandomName } = require('../../util/name-generator');
36
36
 
37
37
  /**
@@ -92,6 +92,7 @@ class SecurityController extends NativeController {
92
92
  'updateRoleMapping',
93
93
  'updateUser',
94
94
  'updateUserMapping',
95
+ 'upsertUser',
95
96
  'validateCredentials'
96
97
  ]);
97
98
 
@@ -770,17 +771,9 @@ class SecurityController extends NativeController {
770
771
  */
771
772
  async createUser (request) {
772
773
  const content = request.getBodyObject('content');
773
- const profileIds = get(content, 'profileIds');
774
+ const profileIds = request.getBodyArray('content.profileIds');
774
775
  const humanReadableId = request.getString('kuid', 'human') !== 'uuid';
775
776
 
776
- if (profileIds === undefined) {
777
- throw kerror.get('api', 'assert', 'missing_argument', 'body.content.profileIds');
778
- }
779
-
780
- if (! Array.isArray(profileIds)) {
781
- throw kerror.get('api', 'assert', 'invalid_type', 'body.content.profileIds', 'array');
782
- }
783
-
784
777
  return this._persistUser(request, profileIds, content, { humanReadableId });
785
778
  }
786
779
 
@@ -819,20 +812,38 @@ class SecurityController extends NativeController {
819
812
  ? null
820
813
  : request.getBodyArray('profileIds');
821
814
 
822
- const updated = await this.ask(
823
- 'core:security:user:update',
824
- id,
825
- profileIds,
826
- content,
827
- {
828
- refresh: request.getRefresh('wait_for'),
829
- retryOnConflict: request.getInteger('retryOnConflict', 10),
830
- userId,
831
- });
815
+ return this._changeUser(request, id, content, userId, profileIds);
816
+ }
832
817
 
833
- global.kuzzle.log.info(`[SECURITY] User "${userId}" applied action "${request.input.action}" on user "${id}."`);
818
+ /**
819
+ * Applies a partial update to an existing user.
820
+ * If the user doesn't already exist, a new user is created.
821
+ *
822
+ * @param {Request} request
823
+ * @returns {Promise}
824
+ */
825
+ async upsertUser (request) {
826
+ const id = request.getId();
827
+ const content = request.getBodyObject('content');
828
+ const userId = request.getKuid();
829
+ const profileIds = request.getBodyArray('content.profileIds');
830
+ const defaultValues = request.getBodyObject('default', {});
834
831
 
835
- return formatProcessing.serializeUser(updated);
832
+ try {
833
+ return await this._changeUser(request, id, content, userId, profileIds);
834
+ }
835
+ catch (error) {
836
+ if (error.id && error.id === 'security.user.not_found') {
837
+ const creatingContent = {
838
+ ...defaultValues,
839
+ ...content, // Order important, content erase default duplicates
840
+ };
841
+
842
+ return this._persistUser(request, profileIds, creatingContent);
843
+ }
844
+
845
+ throw error;
846
+ }
836
847
  }
837
848
 
838
849
  /**
@@ -1226,6 +1237,27 @@ class SecurityController extends NativeController {
1226
1237
  return successes;
1227
1238
  }
1228
1239
 
1240
+ /**
1241
+ * @returns {Promise}
1242
+ * @private
1243
+ */
1244
+ async _changeUser (request, id, content, userId, profileIds) {
1245
+ const updated = await this.ask(
1246
+ 'core:security:user:update',
1247
+ id,
1248
+ profileIds,
1249
+ content,
1250
+ {
1251
+ refresh: request.getRefresh('wait_for'),
1252
+ retryOnConflict: request.getInteger('retryOnConflict', 10),
1253
+ userId,
1254
+ });
1255
+
1256
+ global.kuzzle.log.info(`[SECURITY] User "${userId}" applied action "${request.input.action}" on user "${id}."`);
1257
+
1258
+ return formatProcessing.serializeUser(updated);
1259
+ }
1260
+
1229
1261
  /**
1230
1262
  * @param {Request} request
1231
1263
  * @returns {Promise}
@@ -35,10 +35,10 @@ const {
35
35
  OpenApiDocumentCreate,
36
36
  OpenApiDocumentCreateOrReplace,
37
37
  OpenApiDocumentValidate,
38
+ OpenApiSecurityUpsertUser,
38
39
  OpenApiDocumentmCreateOrReplace,
39
40
  } = require('./openapi/components');
40
41
 
41
-
42
42
  const routes = [
43
43
  // GET (idempotent)
44
44
  { verb: 'get', path: '/_me', controller: 'auth', action: 'getCurrentUser' },
@@ -235,6 +235,7 @@ const routes = [
235
235
  { verb: 'post', path: '/roles/_search', controller: 'security', action: 'searchRoles' },
236
236
  { verb: 'post', path: '/users/_search', controller: 'security', action: 'searchUsers' },
237
237
  { verb: 'post', path: '/credentials/:strategy/users/_search', controller: 'security', action: 'searchUsersByCredentials' },
238
+ { verb: 'post', path: '/users/:_id/_upsert', controller: 'security', action: 'upsertUser', openapi: OpenApiSecurityUpsertUser },
238
239
  { verb: 'post', path: '/credentials/:strategy/:_id/_validate', controller: 'security', action: 'validateCredentials' },
239
240
  { verb: 'post', path: '/_checkRights', controller: 'auth', action: 'checkRights' },
240
241
  { verb: 'post', path: '/_checkRights/:userId', controller: 'security', action: 'checkRights' },
@@ -79,6 +79,9 @@ class OpenApiManager {
79
79
  ...components_1.OpenApiDocumentCreateOrReplaceComponent,
80
80
  ...components_1.OpenApiDocumentCreateComponent,
81
81
  ...components_1.OpenApiDocumentValidateComponent,
82
+ },
83
+ security: {
84
+ ...components_1.OpenApiSecurityUpsertUserComponent,
82
85
  ...components_1.OpenApiDocumentmCreateOrReplaceComponent,
83
86
  }
84
87
  }
@@ -14,7 +14,7 @@ DocumentGet:
14
14
  type: string
15
15
  required: true
16
16
  - in: path
17
- name: documentId
17
+ name: _id
18
18
  schema:
19
19
  type: string
20
20
  required: true
@@ -75,4 +75,4 @@ components:
75
75
  type: "integer"
76
76
  _source:
77
77
  type: string
78
- description: "partial or entire document"
78
+ description: "partial or entire document"
@@ -1,2 +1,3 @@
1
1
  export * from './document';
2
+ export * from './security';
2
3
  export declare const OpenApiPayloadsDefinitions: any;
@@ -17,6 +17,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
17
17
  exports.OpenApiPayloadsDefinitions = void 0;
18
18
  const readYamlFile_1 = require("../../../util/readYamlFile");
19
19
  __exportStar(require("./document"), exports);
20
+ __exportStar(require("./security"), exports);
20
21
  // Document definitions (reusable object for KuzzleRequest and KuzzleResponse)
21
22
  exports.OpenApiPayloadsDefinitions = (0, readYamlFile_1.readYamlFile)(__dirname + '/payloads.yaml').definitions;
22
23
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,2 @@
1
+ export declare const OpenApiSecurityUpsertUser: any;
2
+ export declare const OpenApiSecurityUpsertUserComponent: any;
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OpenApiSecurityUpsertUserComponent = exports.OpenApiSecurityUpsertUser = void 0;
4
+ const readYamlFile_1 = require("../../../../util/readYamlFile");
5
+ // reading the description of the UpsertUser action in the controller security.
6
+ // The yaml objects are then stored in the variables below
7
+ const upsertUserObject = (0, readYamlFile_1.readYamlFile)(__dirname + '/upsertUser.yaml');
8
+ exports.OpenApiSecurityUpsertUser = upsertUserObject.SecurityUpsertUser;
9
+ exports.OpenApiSecurityUpsertUserComponent = upsertUserObject.components.schemas;
10
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,59 @@
1
+ SecurityUpsertUser:
2
+ summary: "Update or create a user."
3
+ tags:
4
+ - user
5
+ parameters:
6
+ - in: path
7
+ name: _id
8
+ schema:
9
+ type: string
10
+ required: true
11
+ - in: path
12
+ name: refresh
13
+ schema:
14
+ type: string
15
+ description: " if set to wait_for, Kuzzle will not respond until the deletion has been indexed"
16
+ required: false
17
+ - in: path
18
+ name: retryOnConflict
19
+ schema:
20
+ type: integer
21
+ description: "conflicts may occur if the same user gets updated multiple times within a short timespan, in a database cluster. You can set the retryOnConflict optional argument (with a retry count), to tell Kuzzle to retry the failing updates the specified amount of times before rejecting the request with an error."
22
+ required: false
23
+ - name: content
24
+ in: "body"
25
+ description: "Updates a user content."
26
+ required: true
27
+ schema:
28
+ $ref: "#/components/security/SecurityUpsertUserRequest"
29
+ responses:
30
+ 200:
31
+ description: "Updates or creates a user."
32
+ schema:
33
+ $ref: "#/components/security/SecurityUpsertUserResponse"
34
+
35
+ components:
36
+ schemas:
37
+ SecurityUpsertUserRequest:
38
+ allOf:
39
+ - type: "object"
40
+ description: "user changes"
41
+ SecurityUpsertUserResponse:
42
+ allOf:
43
+ - $ref: "#/components/ResponsePayload"
44
+ - type: "object"
45
+ properties:
46
+ result:
47
+ type: "object"
48
+ properties:
49
+ _id:
50
+ type: "string"
51
+ description: "userId"
52
+ _version:
53
+ type: "integer"
54
+ _source:
55
+ type: "object"
56
+ description: " (optional) actualized user content. This property appears only if the \"source\" option is set to true"
57
+ created:
58
+ type: "boolean"
59
+
@@ -277,6 +277,27 @@ export declare class KuzzleRequest {
277
277
  * @throws {api.assert.invalid_type} If the fetched parameter is not an object
278
278
  */
279
279
  getObject(name: string, def?: JSONObject | undefined): JSONObject;
280
+ /**
281
+ * Gets a parameter from a request arguments and check with moment.js if the date is an ISO8601 format date
282
+ * or is valid regarding a given custom format (example : YYYY-MM-DD).
283
+ *
284
+ * @param name parameter name.
285
+ * @param format optional parameter to check if the date is valid regarding a format. If not set, the format checked
286
+ * is ISO8601.
287
+ * @throws {api.assert.missing_argument} If parameter not found and no default
288
+ * value provided
289
+ * @throws {api.assert.invalid_type} If parameter value is not a valid date.
290
+ */
291
+ getDate(name: string, format?: string): string;
292
+ /**
293
+ * Gets a parameter from a request arguments and returns it to timestamp format.
294
+ *
295
+ * @param name parameter name.
296
+ * @throws {api.assert.missing_argument} If parameter not found and no default
297
+ * value provided
298
+ * @throws {api.assert.invalid_type} If parameter value is not a valid date.
299
+ */
300
+ getTimestamp(name: string): number;
280
301
  /**
281
302
  * Returns the index specified in the request
282
303
  */
@@ -42,8 +42,14 @@ var __importStar = (this && this.__importStar) || function (mod) {
42
42
  __setModuleDefault(result, mod);
43
43
  return result;
44
44
  };
45
+ var __importDefault = (this && this.__importDefault) || function (mod) {
46
+ return (mod && mod.__esModule) ? mod : { "default": mod };
47
+ };
45
48
  Object.defineProperty(exports, "__esModule", { value: true });
46
49
  exports.Request = exports.KuzzleRequest = void 0;
50
+ const safeObject_1 = require("../../util/safeObject");
51
+ const lodash_1 = require("lodash");
52
+ const moment_1 = __importDefault(require("moment"));
47
53
  const uuid = __importStar(require("uuid"));
48
54
  const nanoid_1 = require("nanoid");
49
55
  const requestInput_1 = require("./requestInput");
@@ -53,8 +59,6 @@ const errors_1 = require("../../kerror/errors");
53
59
  const kerror = __importStar(require("../../kerror"));
54
60
  const types_1 = require("../../types");
55
61
  const assert = __importStar(require("../../util/assertType"));
56
- const safeObject_1 = require("../../util/safeObject");
57
- const lodash_1 = require("lodash");
58
62
  const assertionError = kerror.wrap('api', 'assert');
59
63
  // private properties
60
64
  // \u200b is a zero width space, used to masquerade console.log output
@@ -528,6 +532,48 @@ class KuzzleRequest {
528
532
  getObject(name, def = undefined) {
529
533
  return this._getObject(this.input.args, name, name, def);
530
534
  }
535
+ /**
536
+ * Gets a parameter from a request arguments and check with moment.js if the date is an ISO8601 format date
537
+ * or is valid regarding a given custom format (example : YYYY-MM-DD).
538
+ *
539
+ * @param name parameter name.
540
+ * @param format optional parameter to check if the date is valid regarding a format. If not set, the format checked
541
+ * is ISO8601.
542
+ * @throws {api.assert.missing_argument} If parameter not found and no default
543
+ * value provided
544
+ * @throws {api.assert.invalid_type} If parameter value is not a valid date.
545
+ */
546
+ getDate(name, format) {
547
+ const args = this.input.args;
548
+ if (args[name] === undefined) {
549
+ throw assertionError.get('missing_argument', name);
550
+ }
551
+ if (format && !(0, moment_1.default)(args[name], format, true).isValid()) {
552
+ throw assertionError.get('invalid_type', name, 'date');
553
+ }
554
+ if (!(0, moment_1.default)(args[name], moment_1.default.ISO_8601).isValid()) {
555
+ throw assertionError.get('invalid_type', name, 'date');
556
+ }
557
+ return this.getString(name);
558
+ }
559
+ /**
560
+ * Gets a parameter from a request arguments and returns it to timestamp format.
561
+ *
562
+ * @param name parameter name.
563
+ * @throws {api.assert.missing_argument} If parameter not found and no default
564
+ * value provided
565
+ * @throws {api.assert.invalid_type} If parameter value is not a valid date.
566
+ */
567
+ getTimestamp(name) {
568
+ const args = this.input.args;
569
+ if (args[name] === undefined) {
570
+ throw assertionError.get('missing_argument', name);
571
+ }
572
+ if ((0, moment_1.default)(args[name], true).isValid() === false) {
573
+ throw assertionError.get('invalid_type', name, 'date');
574
+ }
575
+ return this.getInteger(name);
576
+ }
531
577
  /**
532
578
  * Returns the index specified in the request
533
579
  */
@@ -1,5 +1,5 @@
1
- import { RealtimeController, Notification, JSONObject, ScopeOption, UserOption, Kuzzle } from 'kuzzle-sdk';
2
- import { RequestPayload, ResponsePayload } from '../../../types';
1
+ import { RealtimeController, Notification, JSONObject, ScopeOption, UserOption, Kuzzle, RequestPayload } from 'kuzzle-sdk';
2
+ import { ResponsePayload } from '../../../types';
3
3
  interface EmbeddedRealtime extends RealtimeController {
4
4
  /**
5
5
  * Subscribes by providing a set of filters: messages, document changes
@@ -1,5 +1,4 @@
1
- import { KuzzleEventEmitter } from 'kuzzle-sdk';
2
- import { RequestPayload } from '../../../types';
1
+ import { KuzzleEventEmitter, RequestPayload } from 'kuzzle-sdk';
3
2
  export declare class FunnelProtocol extends KuzzleEventEmitter {
4
3
  private id;
5
4
  private connectionId;
@@ -273,7 +273,7 @@
273
273
  "multiple_indice_alias": {
274
274
  "description": "The unique association between an indice and its alias has not been respected",
275
275
  "code": 48,
276
- "message": "An \"%s\" is not allowed to be associated with multiple \"%\". This is probably not linked to this request but rather the consequence of previous actions on ElasticSearch.",
276
+ "message": "An %s is not allowed to be associated with multiple %s. This is probably not linked to this request but rather the consequence of previous actions on ElasticSearch.",
277
277
  "class": "PreconditionError"
278
278
  },
279
279
  "invalid_multi_index_collection_usage": {
@@ -10,7 +10,7 @@ export declare class KuzzleError extends Error {
10
10
  status: number;
11
11
  /**
12
12
  * Error unique code
13
- * @see https://docs.kuzzle.io/core/2/core/2/api/essentials/error-codes/
13
+ * @see https://docs.kuzzle.io/core/2/api/errors/error-codes/
14
14
  */
15
15
  code: number;
16
16
  /**
@@ -47,9 +47,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
47
47
  };
48
48
  Object.defineProperty(exports, "__esModule", { value: true });
49
49
  exports.User = void 0;
50
- const rights_1 = __importDefault(require("./rights"));
51
- const bluebird_1 = __importDefault(require("bluebird"));
52
50
  const lodash_1 = __importDefault(require("lodash"));
51
+ const rights_1 = __importDefault(require("./rights"));
53
52
  const kerror = __importStar(require("../../kerror"));
54
53
  /**
55
54
  * @class User
@@ -73,7 +72,7 @@ class User {
73
72
  */
74
73
  async getRights() {
75
74
  const profiles = await this.getProfiles();
76
- const results = await bluebird_1.default.map(profiles, p => p.getRights());
75
+ const results = await Promise.all(profiles.map(p => p.getRights()));
77
76
  const rights = {};
78
77
  results.forEach(right => lodash_1.default.assignWith(rights, right, rights_1.default.merge));
79
78
  return rights;
@@ -97,15 +96,15 @@ class User {
97
96
  return false;
98
97
  }
99
98
  // Every target must be allowed by at least one profile
100
- return this.areTargetsAllowed(profiles, targets);
99
+ return this.areTargetsAllowed(request, profiles, targets);
101
100
  }
102
101
  /**
103
102
  * Verifies that every targets are allowed by at least one profile,
104
103
  * while skipping the ones that includes a wildcard since they will be expanded
105
104
  * later on, based on index and collections authorized for the given user.
106
105
  */
107
- async areTargetsAllowed(profiles, targets) {
108
- const profilesPolicies = await bluebird_1.default.map(profiles, profile => profile.getAllowedPolicies());
106
+ async areTargetsAllowed(request, profiles, targets) {
107
+ const profilesPolicies = await Promise.all(profiles.map(profile => profile.getAllowedPolicies(request)));
109
108
  // Every target must be allowed by at least one profile
110
109
  for (const target of targets) {
111
110
  // Skip targets with no Index or Collection