passbolt-browser-extension 5.12.1 → 5.13.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 (198) hide show
  1. package/.devcontainer/safe-chain-config.json +2 -1
  2. package/.github/ISSUE_TEMPLATE/bug_report.md +30 -22
  3. package/.github/ISSUE_TEMPLATE/config.yml +11 -0
  4. package/.jpmignore +0 -1
  5. package/CHANGELOG.md +82 -1
  6. package/CONTRIBUTING.md +1 -12
  7. package/README.md +5 -16
  8. package/RELEASE_NOTES.md +2 -3
  9. package/SECURITY.md +7 -0
  10. package/i18next.config.js +28 -0
  11. package/jest.config.json +1 -0
  12. package/package.json +20 -40
  13. package/src/all/background_page/controller/accountRecovery/reviewRequestController.test.js +1 -1
  14. package/src/all/background_page/{event → controller/actionLog}/findAllForActionLogController.js +1 -1
  15. package/src/all/background_page/{event → controller/actionLog}/findAllForActionLogController.test.js +4 -4
  16. package/src/all/background_page/controller/export/exportResourcesFileController.test.js +7 -2
  17. package/src/all/background_page/controller/folder/folderCreateController.js +3 -1
  18. package/src/all/background_page/controller/group/findMyGroupsController.test.js +2 -2
  19. package/src/all/background_page/controller/group/getOrFindGroupsController.js +61 -0
  20. package/src/all/background_page/controller/group/getOrFindGroupsController.test.js +69 -0
  21. package/src/all/background_page/controller/group/getOrFindGroupsUsersController.js +62 -0
  22. package/src/all/background_page/controller/group/getOrFindGroupsUsersController.test.js +69 -0
  23. package/src/all/background_page/controller/group/groupCreateController.js +1 -1
  24. package/src/all/background_page/controller/group/groupCreateController.test.js +1 -1
  25. package/src/all/background_page/controller/group/groupUpdateController.js +1 -1
  26. package/src/all/background_page/controller/group/updateAllGroupsLocalStorageController.test.js +1 -1
  27. package/src/all/background_page/controller/keyring/synchroniseKeyringController.js +51 -0
  28. package/src/all/background_page/controller/keyring/synchroniseKeyringController.test.js +49 -0
  29. package/src/all/background_page/controller/metadata/shareMetadataKeyPrivateController.test.js +1 -1
  30. package/src/all/background_page/controller/move/moveFolderController.js +0 -2
  31. package/src/all/background_page/controller/permission/FindAcoPermissionsForDisplayController.js +1 -1
  32. package/src/all/background_page/controller/permission/FindAcoPermissionsForDisplayController.test.js +2 -2
  33. package/src/all/background_page/controller/resource/findAllByIdsForDisplayPermissionsController.test.js +8 -3
  34. package/src/all/background_page/controller/resource/findAllIdsByIsSharedWithGroupController.test.js +9 -4
  35. package/src/all/background_page/controller/resource/resourceUpdateController.test.js +1 -2
  36. package/src/all/background_page/controller/resourceLocalStorage/resourceUpdateLocalStorageController.test.js +5 -2
  37. package/src/all/background_page/controller/share/findFoldersForShareController.js +66 -0
  38. package/src/all/background_page/controller/share/findFoldersForShareController.test.js +70 -0
  39. package/src/all/background_page/controller/share/searchUsersAndGroupsController.js +4 -4
  40. package/src/all/background_page/controller/share/searchUsersAndGroupsController.test.js +8 -23
  41. package/src/all/background_page/controller/share/shareResourcesController.test.js +2 -2
  42. package/src/all/background_page/controller/subscription/createSubscriptionKeyController.js +63 -0
  43. package/src/all/background_page/controller/subscription/createSubscriptionKeyController.test.js +56 -0
  44. package/src/all/background_page/controller/subscription/deleteSubscriptionKeyController.js +55 -0
  45. package/src/all/background_page/controller/subscription/deleteSubscriptionKeyController.test.js +50 -0
  46. package/src/all/background_page/controller/user/deleteUserController.test.js +1 -1
  47. package/src/all/background_page/controller/user/getOrFindUsersController.js +61 -0
  48. package/src/all/background_page/controller/user/getOrFindUsersController.test.js +69 -0
  49. package/src/all/background_page/error/deleteDryRunError.js +1 -1
  50. package/src/all/background_page/event/actionLogEvents.js +1 -1
  51. package/src/all/background_page/event/appEvents.js +25 -0
  52. package/src/all/background_page/event/groupEvents.js +26 -0
  53. package/src/all/background_page/event/keyringEvents.js +12 -0
  54. package/src/all/background_page/event/shareEvents.js +3 -9
  55. package/src/all/background_page/event/userEvents.js +13 -0
  56. package/src/all/background_page/model/config.js +12 -2
  57. package/src/all/background_page/model/entity/folder/folderEntity.js +2 -2
  58. package/src/all/background_page/model/entity/folder/folderEntity.test.js +2 -2
  59. package/src/all/background_page/model/entity/folder/foldersCollection.test.js +1 -1
  60. package/src/all/background_page/model/entity/group/update/groupUpdateEntity.js +1 -1
  61. package/src/all/background_page/model/entity/group/update/groupUpdateEntity.test.js +1 -1
  62. package/src/all/background_page/model/entity/permission/actionLog/updatedPermissionEntity.js +2 -2
  63. package/src/all/background_page/model/entity/permission/actionLog/updatedPermissionEntity.test.data.js +1 -1
  64. package/src/all/background_page/model/entity/permission/change/permissionChangeEntity.js +1 -1
  65. package/src/all/background_page/model/entity/permission/change/permissionChangesCollection.js +2 -2
  66. package/src/all/background_page/model/entity/permission/change/permissionChangesCollection.test.js +2 -2
  67. package/src/all/background_page/model/entity/resource/resourceEntity.js +2 -2
  68. package/src/all/background_page/model/entity/resource/resourceEntity.test.js +1 -1
  69. package/src/all/background_page/model/entity/user/userEntity.js +16 -377
  70. package/src/all/background_page/model/entity/user/userEntity.test.js +22 -297
  71. package/src/all/background_page/model/entity/userAndGroupSearchResultEntity/userAndGroupSearchResultEntity.js +1 -1
  72. package/src/all/background_page/model/folder/folderModel.js +5 -154
  73. package/src/all/background_page/model/group/groupModel.js +1 -1
  74. package/src/all/background_page/model/keyring.js +52 -17
  75. package/src/all/background_page/model/keyring.test.js +110 -0
  76. package/src/all/background_page/model/resource/resourceModel.js +2 -55
  77. package/src/all/background_page/model/setup/setupModel.js +2 -2
  78. package/src/all/background_page/model/user/userModel.js +18 -15
  79. package/src/all/background_page/model/user/userModel.test.js +1 -3
  80. package/src/all/background_page/service/api/abstract/abstractService.js +3 -17
  81. package/src/all/background_page/service/api/edition/passboltEditionApiService.js +64 -0
  82. package/src/all/background_page/service/api/edition/passboltEditionApiService.test.js +99 -0
  83. package/src/all/background_page/service/api/group/groupApiService.js +22 -23
  84. package/src/all/background_page/service/api/group/groupApiService.test.js +70 -0
  85. package/src/all/background_page/service/api/resource/resourceService.js +18 -12
  86. package/src/all/background_page/service/api/share/{shareService.js → shareApiService.js} +10 -7
  87. package/src/all/background_page/service/api/share/{shareService.test.js → shareApiService.test.js} +5 -5
  88. package/src/all/background_page/service/group/createGroupService.js +1 -1
  89. package/src/all/background_page/service/group/createGroupService.test.js +1 -1
  90. package/src/all/background_page/service/group/findAndUpdateGroupsLocalStorageService.js +56 -1
  91. package/src/all/background_page/service/group/findAndUpdateGroupsLocalStorageService.test.js +84 -2
  92. package/src/all/background_page/service/group/findGroupsService.js +5 -9
  93. package/src/all/background_page/service/group/findGroupsService.test.data.js +1 -1
  94. package/src/all/background_page/service/group/findGroupsService.test.js +10 -15
  95. package/src/all/background_page/service/group/getOrFindGroupsService.js +65 -0
  96. package/src/all/background_page/service/group/getOrFindGroupsService.test.js +168 -0
  97. package/src/all/background_page/service/group/getOrFindGroupsUsersService.js +51 -0
  98. package/src/all/background_page/service/group/getOrFindGroupsUsersService.test.js +94 -0
  99. package/src/all/background_page/service/group/groupUpdateService.js +5 -3
  100. package/src/all/background_page/service/group/groupUpdateService.test.js +10 -2
  101. package/src/all/background_page/service/local_storage/groupLocalStorage.js +2 -2
  102. package/src/all/background_page/service/local_storage/groupLocalStorage.test.js +3 -3
  103. package/src/all/background_page/service/local_storage/userLocalStorage.js +57 -36
  104. package/src/all/background_page/service/local_storage/userLocalStorage.test.js +282 -0
  105. package/src/all/background_page/service/metadata/createMetadataKeyService.test.js +1 -1
  106. package/src/all/background_page/service/metadata/saveMetadataSettingsService.test.js +1 -1
  107. package/src/all/background_page/service/metadata/shareMetadataKeyPrivateService.test.js +1 -1
  108. package/src/all/background_page/service/migrateMetadata/migrateMetadataResourcesService.js +1 -1
  109. package/src/all/background_page/service/move/calculatePermissionsChangesForMoveService.js +113 -0
  110. package/src/all/background_page/service/move/calculatePermissionsChangesForMoveService.test.data.js +38 -0
  111. package/src/all/background_page/service/move/calculatePermissionsChangesForMoveService.test.js +158 -0
  112. package/src/all/background_page/service/move/moveOneFolderService.js +6 -7
  113. package/src/all/background_page/service/move/moveOneFolderService.test.js +90 -90
  114. package/src/all/background_page/service/move/moveResourcesService.js +2 -5
  115. package/src/all/background_page/service/permission/findPermissionsService.js +1 -1
  116. package/src/all/background_page/service/resource/create/resourceCreateService.js +13 -31
  117. package/src/all/background_page/service/resource/create/resourceCreateService.test.js +25 -18
  118. package/src/all/background_page/service/resource/export/exportResourcesService.test.js +13 -4
  119. package/src/all/background_page/service/resource/findAndUpdateResourcesLocalStorageService.test.js +35 -28
  120. package/src/all/background_page/service/resource/findResourcesService.js +78 -2
  121. package/src/all/background_page/service/resource/findResourcesService.test.data.js +1 -1
  122. package/src/all/background_page/service/resource/findResourcesService.test.js +90 -31
  123. package/src/all/background_page/service/resource/getOrFindResourcesService.test.js +18 -8
  124. package/src/all/background_page/service/session_storage/keepSessionAliveService.js +3 -3
  125. package/src/all/background_page/service/session_storage/keepSessionAliveService.test.js +5 -3
  126. package/src/all/background_page/service/share/searchUsersAndGroupsService.js +41 -0
  127. package/src/all/background_page/service/share/searchUsersAndGroupsService.test.js +64 -0
  128. package/src/all/background_page/service/share/shareFoldersService.js +3 -3
  129. package/src/all/background_page/service/share/shareFoldersService.test.js +3 -3
  130. package/src/all/background_page/service/share/shareResourceService.js +4 -4
  131. package/src/all/background_page/service/share/shareResourceService.test.js +8 -8
  132. package/src/all/background_page/service/subscription/createSubscriptionKeyService.js +57 -0
  133. package/src/all/background_page/service/subscription/createSubscriptionKeyService.test.js +111 -0
  134. package/src/all/background_page/service/subscription/deleteSubscriptionKeyService.js +35 -0
  135. package/src/all/background_page/service/subscription/deleteSubscriptionKeyService.test.js +55 -0
  136. package/src/all/background_page/service/user/deleteUserService.js +4 -4
  137. package/src/all/background_page/service/user/deleteUserService.test.js +10 -10
  138. package/src/all/background_page/service/user/findAndUpdateUsersLocalStorageService.js +81 -0
  139. package/src/all/background_page/service/user/findAndUpdateUsersLocalStorageService.test.js +132 -0
  140. package/src/all/background_page/service/user/findUsersService.js +6 -6
  141. package/src/all/background_page/service/user/findUsersService.test.js +39 -38
  142. package/src/all/background_page/service/user/getOrFindUsersService.js +60 -0
  143. package/src/all/background_page/service/user/getOrFindUsersService.test.js +110 -0
  144. package/src/all/background_page/utils/assertions.js +1 -0
  145. package/src/all/locales/ko-KR/common.json +1 -1
  146. package/src/chrome/manifest.json +1 -1
  147. package/src/chrome-mv3/manifest.json +1 -1
  148. package/src/firefox/manifest.json +3 -3
  149. package/src/safari/manifest.json +2 -2
  150. package/test/jest.env-setup.js +31 -0
  151. package/webpack/applyOutputClean.js +69 -0
  152. package/webpack/base.config.js +33 -0
  153. package/webpack/common-blocks.js +91 -0
  154. package/webpack/expectedBuildArtifacts.js +51 -0
  155. package/webpack/i18nextExtractionPlugin.js +43 -0
  156. package/webpack/passboltEnvPlugin.js +41 -0
  157. package/webpack/webExtPlugin/index.js +75 -0
  158. package/webpack.chromium-mv2.config.js +40 -0
  159. package/webpack.chromium-mv3.config.js +40 -0
  160. package/webpack.common.config.js +186 -0
  161. package/webpack.config.js +38 -0
  162. package/webpack.firefox.config.js +40 -0
  163. package/webpack.mv2.config.js +65 -0
  164. package/webpack.mv3.config.js +99 -0
  165. package/webpack.safari-background-page.config.js +66 -57
  166. package/webpack.safari.config.js +44 -0
  167. package/Gruntfile.js +0 -471
  168. package/am_i_compromised.py +0 -1036
  169. package/am_i_compromised.sh +0 -688
  170. package/i18next-parser.config.js +0 -22
  171. package/src/all/background_page/config/config.json +0 -7
  172. package/src/all/background_page/config/config.json.debug +0 -7
  173. package/src/all/background_page/config/config.json.default +0 -7
  174. package/src/all/background_page/model/entity/group/groupEntity.js +0 -241
  175. package/src/all/background_page/model/entity/group/groupEntity.test.js +0 -136
  176. package/src/all/background_page/model/entity/group/groupsCollection.js +0 -166
  177. package/src/all/background_page/model/entity/group/groupsCollection.test.data.js +0 -34
  178. package/src/all/background_page/model/entity/group/groupsCollection.test.js +0 -227
  179. package/src/all/background_page/model/entity/permission/permissionEntity.js +0 -485
  180. package/src/all/background_page/model/entity/permission/permissionEntity.test.js +0 -263
  181. package/src/all/background_page/model/entity/permission/permissionsCollection.js +0 -486
  182. package/src/all/background_page/model/entity/permission/permissionsCollection.test.js +0 -700
  183. package/src/all/background_page/model/entity/user/usersCollection.js +0 -147
  184. package/src/all/background_page/model/entity/user/usersCollection.test.js +0 -223
  185. package/src/all/background_page/model/share/shareModel.js +0 -183
  186. package/src/all/background_page/model/share/shareModel.test.js +0 -61
  187. package/src/all/background_page/service/api/user/userService.js +0 -260
  188. package/src/all/background_page/service/resource/create/resourceCreateService.test.data.js +0 -55
  189. package/webpack-content-scripts.browser-integration.config.js +0 -57
  190. package/webpack-content-scripts.config.js +0 -61
  191. package/webpack-content-scripts.public-website-sign-in.config.js +0 -57
  192. package/webpack-data.config.js +0 -102
  193. package/webpack-data.download.config.js +0 -59
  194. package/webpack-data.in-form-call-to-action.config.js +0 -97
  195. package/webpack-data.in-form-menu.config.js +0 -97
  196. package/webpack-offscreens.config.js +0 -55
  197. package/webpack.background-page.config.js +0 -62
  198. package/webpack.service-worker.config.js +0 -65
@@ -32,6 +32,8 @@ const PRIVATE = "PRIVATE";
32
32
  const MY_KEY_ID = "MY_KEY_ID";
33
33
  const STORAGE_KEY_PUBLIC = "passbolt-public-gpgkeys";
34
34
  const STORAGE_KEY_PRIVATE = "passbolt-private-gpgkeys";
35
+ // Number of public keys parsed concurrently during a keyring sync, to bound peak memory.
36
+ const SYNC_CHUNK_SIZE = 500;
35
37
 
36
38
  /**
37
39
  * The class that deals with Passbolt Keyring.
@@ -88,6 +90,31 @@ class Keyring {
88
90
  * if the user id is not valid
89
91
  */
90
92
  async importPublic(armoredPublicKey, userId) {
93
+ const keyInfo = await this.buildPublicKeyInfo(armoredPublicKey, userId);
94
+
95
+ // Add the key in the keyring.
96
+ const publicKeys = this.getPublicKeysFromStorage();
97
+ publicKeys[userId] = keyInfo;
98
+ this.store(Keyring.PUBLIC, publicKeys);
99
+
100
+ return true;
101
+ }
102
+
103
+ /**
104
+ * Validate, parse and read the metadata of a public armored key, without persisting it.
105
+ * Used by importPublic (single key) and sync (batch), so the keyring is stored only once
106
+ * instead of once per key.
107
+ *
108
+ * @param {string} armoredPublicKey The key to read
109
+ * @param {string} userId The owner of the key
110
+ * @returns {Promise<object>} The key info DTO, with its user_id set.
111
+ * @throw Error
112
+ * if the key cannot be read by openpgp
113
+ * if the key is not public
114
+ * if the user id is not valid
115
+ * @private
116
+ */
117
+ async buildPublicKeyInfo(armoredPublicKey, userId) {
91
118
  // Check user id
92
119
  if (typeof userId === "undefined") {
93
120
  throw new Error("The user id is undefined");
@@ -107,14 +134,9 @@ class Keyring {
107
134
 
108
135
  // Get the keyInfo.
109
136
  const keyInfo = (await GetGpgKeyInfoService.getKeyInfo(primaryPublicKey)).toDto();
137
+ keyInfo.user_id = userId;
110
138
 
111
- // Add the key in the keyring.
112
- const publicKeys = this.getPublicKeysFromStorage();
113
- publicKeys[userId] = keyInfo;
114
- publicKeys[userId].user_id = userId;
115
- this.store(Keyring.PUBLIC, publicKeys);
116
-
117
- return true;
139
+ return keyInfo;
118
140
  }
119
141
 
120
142
  /**
@@ -259,20 +281,25 @@ class Keyring {
259
281
  throw new Error("Could not synchronize the keyring. The server response body is missing.");
260
282
  }
261
283
 
262
- // Store all the new keys in the keyring.
263
- let meta, i;
264
- const imports = [];
265
- for (i in json.body) {
266
- if (Object.prototype.hasOwnProperty.call(json.body, i)) {
267
- meta = json.body[i];
268
- imports.push(this.importPublic(meta.armored_key, meta.user_id));
269
- }
284
+ /*
285
+ * Build the keyring in memory and persist it once, rather than reading and re-serializing
286
+ * the whole keyring for every key (which is quadratic and exhausts memory on large orgs).
287
+ * Keys are read in bounded-size batches so we never parse the entire payload at once.
288
+ */
289
+ const publicKeys = this.getPublicKeysFromStorage();
290
+ const metas = Object.values(json.body).filter((meta) => meta?.armored_key && meta?.user_id);
291
+ for (let i = 0; i < metas.length; i += Keyring.SYNC_CHUNK_SIZE) {
292
+ const chunk = metas.slice(i, i + Keyring.SYNC_CHUNK_SIZE);
293
+ const keysInfo = await Promise.all(chunk.map((meta) => this.buildPublicKeyInfo(meta.armored_key, meta.user_id)));
294
+ keysInfo.forEach((keyInfo) => {
295
+ publicKeys[keyInfo.user_id] = keyInfo;
296
+ });
270
297
  }
271
- await Promise.all(imports);
298
+ this.store(Keyring.PUBLIC, publicKeys);
272
299
 
273
300
  storage.setItem("latestSync", json.header.servertime);
274
301
 
275
- return json.body.length;
302
+ return metas.length;
276
303
  }
277
304
 
278
305
  /*
@@ -402,6 +429,14 @@ class Keyring {
402
429
  return PUBLIC;
403
430
  }
404
431
 
432
+ /**
433
+ * Keyring.SYNC_CHUNK_SIZE
434
+ * @returns {number}
435
+ */
436
+ static get SYNC_CHUNK_SIZE() {
437
+ return SYNC_CHUNK_SIZE;
438
+ }
439
+
405
440
  /**
406
441
  * Keyring.PRIVATE
407
442
  * @returns {string}
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Passbolt ~ Open source password manager for teams
3
+ * Copyright (c) Passbolt SA (https://www.passbolt.com)
4
+ *
5
+ * Licensed under GNU Affero General Public License version 3 of the or any later version.
6
+ * For full copyright and license information, please see the LICENSE.txt
7
+ * Redistributions of files must retain the above copyright notice.
8
+ *
9
+ * @copyright Copyright (c) Passbolt SA (https://www.passbolt.com)
10
+ * @license https://opensource.org/licenses/AGPL-3.0 AGPL License
11
+ * @link https://www.passbolt.com Passbolt(tm)
12
+ * @since 5.13.0
13
+ */
14
+ import { enableFetchMocks } from "jest-fetch-mock";
15
+ import { v4 as uuidv4 } from "uuid";
16
+ import { pgpKeys } from "passbolt-styleguide/test/fixture/pgpKeys/keys";
17
+ import Keyring from "./keyring";
18
+ import UserSettings from "./userSettings/userSettings";
19
+
20
+ beforeAll(() => {
21
+ enableFetchMocks();
22
+ });
23
+
24
+ beforeEach(() => {
25
+ jest.restoreAllMocks();
26
+ fetch.resetMocks();
27
+ jest.spyOn(UserSettings.prototype, "getDomain").mockImplementation(() => "https://passbolt.dev");
28
+ new Keyring().flush(Keyring.PUBLIC);
29
+ });
30
+
31
+ const syncResponse = (body) => JSON.stringify({ header: { servertime: "2026-06-02T00:00:00+00:00" }, body });
32
+
33
+ describe("Keyring", () => {
34
+ describe("::sync", () => {
35
+ it("should store every synced public key, tagged with its user id", async () => {
36
+ expect.assertions(5);
37
+ const keyring = new Keyring();
38
+ const adaId = uuidv4();
39
+ const bettyId = uuidv4();
40
+ fetch.mockResponseOnce(
41
+ syncResponse([
42
+ { user_id: adaId, armored_key: pgpKeys.ada.public },
43
+ { user_id: bettyId, armored_key: pgpKeys.betty.public },
44
+ ]),
45
+ );
46
+
47
+ const count = await keyring.sync();
48
+
49
+ const publicKeys = keyring.getPublicKeysFromStorage();
50
+ expect(count).toEqual(2);
51
+ expect(publicKeys[adaId]).toBeDefined();
52
+ expect(publicKeys[adaId].user_id).toEqual(adaId);
53
+ expect(publicKeys[bettyId]).toBeDefined();
54
+ expect(publicKeys[bettyId].user_id).toEqual(bettyId);
55
+ });
56
+
57
+ it("should persist the keyring only once, not once per key (O(n^2) regression guard)", async () => {
58
+ expect.assertions(2);
59
+ const keyring = new Keyring();
60
+ // Reuse one armored key across many distinct users — we only assert the persistence strategy.
61
+ const body = Array.from({ length: 50 }, () => ({ user_id: uuidv4(), armored_key: pgpKeys.ada.public }));
62
+ fetch.mockResponseOnce(syncResponse(body));
63
+ const storeSpy = jest.spyOn(Keyring.prototype, "store");
64
+
65
+ await keyring.sync();
66
+
67
+ expect(Object.keys(keyring.getPublicKeysFromStorage())).toHaveLength(50);
68
+ // The bug stored the whole keyring once per key (50 times); the fix stores once.
69
+ expect(storeSpy).toHaveBeenCalledTimes(1);
70
+ });
71
+
72
+ it("should ignore body entries missing an armored key or user id", async () => {
73
+ expect.assertions(2);
74
+ const keyring = new Keyring();
75
+ const adaId = uuidv4();
76
+ fetch.mockResponseOnce(
77
+ syncResponse([
78
+ { user_id: adaId, armored_key: pgpKeys.ada.public },
79
+ { user_id: uuidv4() }, // missing armored_key
80
+ { armored_key: pgpKeys.betty.public }, // missing user_id
81
+ ]),
82
+ );
83
+
84
+ const count = await keyring.sync();
85
+
86
+ expect(count).toEqual(1);
87
+ expect(keyring.getPublicKeysFromStorage()[adaId]).toBeDefined();
88
+ });
89
+ });
90
+
91
+ describe("::importPublic", () => {
92
+ it("should still import and store a single public key", async () => {
93
+ expect.assertions(2);
94
+ const keyring = new Keyring();
95
+ const adaId = uuidv4();
96
+ const storeSpy = jest.spyOn(Keyring.prototype, "store");
97
+
98
+ await keyring.importPublic(pgpKeys.ada.public, adaId);
99
+
100
+ expect(keyring.getPublicKeysFromStorage()[adaId].user_id).toEqual(adaId);
101
+ expect(storeSpy).toHaveBeenCalledTimes(1);
102
+ });
103
+
104
+ it("should reject an invalid user id", async () => {
105
+ expect.assertions(1);
106
+ const keyring = new Keyring();
107
+ await expect(keyring.importPublic(pgpKeys.ada.public, "not-a-uuid")).rejects.toThrow("The user id is not valid");
108
+ });
109
+ });
110
+ });
@@ -13,8 +13,8 @@
13
13
  import ResourceLocalStorage from "../../service/local_storage/resourceLocalStorage";
14
14
  import ResourceTypeModel from "../../model/resourceType/resourceTypeModel";
15
15
  import ResourcesCollection from "../entity/resource/resourcesCollection";
16
- import PermissionEntity from "../entity/permission/permissionEntity";
17
- import PermissionsCollection from "../entity/permission/permissionsCollection";
16
+ import PermissionEntity from "passbolt-styleguide/src/shared/models/entity/permission/permissionEntity";
17
+ import PermissionsCollection from "passbolt-styleguide/src/shared/models/entity/permission/permissionsCollection";
18
18
  import ResourceEntity from "../entity/resource/resourceEntity";
19
19
  import PermissionChangesCollection from "../entity/permission/change/permissionChangesCollection";
20
20
  import ResourceService from "../../service/api/resource/resourceService";
@@ -88,59 +88,6 @@ class ResourceModel {
88
88
  * Permission changes
89
89
  * ==============================================================
90
90
  */
91
- /**
92
- * Calculate permission changes for a move
93
- * From current permissions, remove the parent folder permissions, add the destination permissions
94
- * From this new set of permission and the original permission calculate the needed changed
95
- *
96
- * NOTE: This function requires permissions to be set for all objects
97
- *
98
- * @param {ResourceEntity} resource
99
- * @param {(FolderEntity|null)} parentFolder
100
- * @param {(FolderEntity|null)} destFolder
101
- * @returns {PermissionChangesCollection}
102
- */
103
- calculatePermissionsChangesForMove(resource, parentFolder, destFolder) {
104
- let remainingPermissions = new PermissionsCollection([], { assertAtLeastOneOwner: false });
105
-
106
- // Remove permissions from parent if any
107
- if (parentFolder !== null) {
108
- if (!resource.permissions || !parentFolder.permissions) {
109
- throw new TypeError("Resource model calculatePermissionsChangesForMove requires permissions to be set.");
110
- }
111
- remainingPermissions = PermissionsCollection.diff(resource.permissions, parentFolder.permissions, false);
112
- }
113
- // Add parent permissions
114
- let permissionsFromParent = new PermissionsCollection([], { assertAtLeastOneOwner: false });
115
- if (destFolder) {
116
- if (!destFolder.permissions) {
117
- throw new TypeError(
118
- "Resource model calculatePermissionsChangesForMove requires destination permissions to be set.",
119
- );
120
- }
121
- permissionsFromParent = destFolder.permissions.cloneForAco(PermissionEntity.ACO_RESOURCE, resource.id, false);
122
- }
123
-
124
- const newPermissions = PermissionsCollection.sum(remainingPermissions, permissionsFromParent, false);
125
- if (!destFolder) {
126
- /*
127
- * If the move is toward the root
128
- * Reuse highest permission
129
- */
130
- newPermissions.addOrReplace(
131
- new PermissionEntity({
132
- aco: PermissionEntity.ACO_RESOURCE,
133
- aro: resource.permission.aro,
134
- aco_foreign_key: resource.id,
135
- aro_foreign_key: resource.permission.aroForeignKey,
136
- type: PermissionEntity.PERMISSION_OWNER,
137
- }),
138
- );
139
- }
140
- newPermissions.assertAtLeastOneOwner();
141
- return PermissionChangesCollection.calculateChanges(resource.permissions, newPermissions);
142
- }
143
-
144
91
  /**
145
92
  * Calculate permission changes for a create
146
93
  * From current permissions add the destination permissions
@@ -11,7 +11,7 @@
11
11
  * @link https://www.passbolt.com Passbolt(tm)
12
12
  */
13
13
  import AccountRecoveryOrganizationPolicyEntity from "../entity/accountRecovery/accountRecoveryOrganizationPolicyEntity";
14
- import UserService from "../../service/api/user/userService";
14
+ import UserApiService from "passbolt-styleguide/src/shared/services/api/user/userApiService";
15
15
  import SetupService from "../../service/api/setup/setupService";
16
16
  import UserEntity from "../entity/user/userEntity";
17
17
  import Validator from "validator";
@@ -26,7 +26,7 @@ class SetupModel {
26
26
  */
27
27
  constructor(apiClientOptions) {
28
28
  this.setupService = new SetupService(apiClientOptions);
29
- this.userService = new UserService(apiClientOptions);
29
+ this.userService = new UserApiService(apiClientOptions);
30
30
  }
31
31
 
32
32
  /**
@@ -12,9 +12,9 @@
12
12
  * @since 3.0.0
13
13
  */
14
14
  import UserLocalStorage from "../../service/local_storage/userLocalStorage";
15
- import UserService from "../../service/api/user/userService";
15
+ import UserApiService from "passbolt-styleguide/src/shared/services/api/user/userApiService";
16
16
  import UserEntity from "../entity/user/userEntity";
17
- import UsersCollection from "../entity/user/usersCollection";
17
+ import UsersCollection from "passbolt-styleguide/src/shared/models/entity/user/usersCollection";
18
18
  import Validator from "validator";
19
19
  import RoleEntity from "passbolt-styleguide/src/shared/models/entity/role/roleEntity";
20
20
  import UserMeSessionStorageService from "../../service/sessionStorage/userMeSessionStorageService";
@@ -32,7 +32,7 @@ class UserModel {
32
32
  * @public
33
33
  */
34
34
  constructor(apiClientOptions, account = null) {
35
- this.userService = new UserService(apiClientOptions);
35
+ this.userApiService = new UserApiService(apiClientOptions);
36
36
  this.organisationSettingsModel = new OrganizationSettingsModel(apiClientOptions);
37
37
  this.account = account;
38
38
  }
@@ -62,7 +62,7 @@ class UserModel {
62
62
  contains.missing_metadata_key_ids = true;
63
63
  }
64
64
  }
65
- const usersCollection = await this.findAll(contains, null, null, true);
65
+ const usersCollection = await this.findAll(contains, null, true);
66
66
  await UserLocalStorage.set(usersCollection);
67
67
  return usersCollection;
68
68
  }
@@ -75,7 +75,7 @@ class UserModel {
75
75
  * @public
76
76
  */
77
77
  async resendInvite(username) {
78
- return this.userService.resendInvite(username);
78
+ return this.userApiService.resendInvite(username);
79
79
  }
80
80
 
81
81
  /**
@@ -128,7 +128,7 @@ class UserModel {
128
128
  * @returns {Promise<UserEntity>}
129
129
  */
130
130
  async findOne(userId, contains, ignoreInvalidEntity) {
131
- const userDto = await this.userService.get(userId, contains);
131
+ const userDto = await this.userApiService.get(userId, contains);
132
132
  return new UserEntity(userDto, { ignoreInvalidEntity: ignoreInvalidEntity });
133
133
  }
134
134
 
@@ -137,12 +137,11 @@ class UserModel {
137
137
  *
138
138
  * @param {Object} [contains] optional example: {groups_users: true}
139
139
  * @param {Object} [filters] optional
140
- * @param {Object} [orders] optional
141
140
  * @param {boolean?} [ignoreInvalidEntity] Should invalid entities be ignored.
142
141
  * @returns {Promise<UsersCollection>}
143
142
  */
144
- async findAll(contains, filters, orders, ignoreInvalidEntity) {
145
- const usersDto = await this.userService.findAll(contains, filters, orders);
143
+ async findAll(contains, filters, ignoreInvalidEntity) {
144
+ const usersDto = (await this.userApiService.findAll(contains, filters)).body ?? [];
146
145
  return new UsersCollection(usersDto, { clone: false, ignoreInvalidEntity: ignoreInvalidEntity });
147
146
  }
148
147
 
@@ -157,9 +156,9 @@ class UserModel {
157
156
  if (!Validator.isUUID(userId)) {
158
157
  throw new TypeError("Error in find all users for users updates. The user id is not a valid uuid.");
159
158
  }
160
- const usersDto = await this.userService.findAll(null, { "has-access": userId });
159
+ const usersDto = (await this.userApiService.findAll(null, { "has-access": userId })).body ?? [];
161
160
  const usersCollection = new UsersCollection(usersDto);
162
- return usersCollection.ids;
161
+ return usersCollection.extract("id");
163
162
  }
164
163
 
165
164
  /*
@@ -176,7 +175,7 @@ class UserModel {
176
175
  */
177
176
  async create(userEntity) {
178
177
  const data = userEntity.toDto({ profile: { avatar: false } });
179
- const userDto = await this.userService.create(data);
178
+ const userDto = await this.userApiService.create(data);
180
179
  const newUserEntity = new UserEntity(userDto);
181
180
  await UserLocalStorage.addUser(newUserEntity);
182
181
  return newUserEntity;
@@ -192,7 +191,7 @@ class UserModel {
192
191
  */
193
192
  async update(userEntity, ignoreInvalidEntity) {
194
193
  const data = userEntity.toDto({ profile: { avatar: false } });
195
- const userDto = await this.userService.update(userEntity.id, data);
194
+ const userDto = await this.userApiService.update(userEntity.id, data);
196
195
  const updatedUserEntity = new UserEntity(userDto, { ignoreInvalidEntity });
197
196
  await UserLocalStorage.updateUser(updatedUserEntity);
198
197
  return updatedUserEntity;
@@ -208,7 +207,11 @@ class UserModel {
208
207
  * @public
209
208
  */
210
209
  async updateAvatar(userId, avatarUpdateEntity, ignoreInvalidEntity) {
211
- const userDto = await this.userService.updateAvatar(userId, avatarUpdateEntity.file, avatarUpdateEntity.filename);
210
+ const userDto = await this.userApiService.updateAvatar(
211
+ userId,
212
+ avatarUpdateEntity.file,
213
+ avatarUpdateEntity.filename,
214
+ );
212
215
  return new UserEntity(userDto, { ignoreInvalidEntity });
213
216
  }
214
217
 
@@ -222,7 +225,7 @@ class UserModel {
222
225
  username: account.username,
223
226
  case: "lost-passphrase",
224
227
  };
225
- await this.userService.requestHelpCredentialsLost(requestHelpDto);
228
+ await this.userApiService.requestHelpCredentialsLost(requestHelpDto);
226
229
  }
227
230
  }
228
231
 
@@ -169,7 +169,6 @@ describe("UserModel", () => {
169
169
  profile: true,
170
170
  },
171
171
  null,
172
- null,
173
172
  true,
174
173
  );
175
174
  });
@@ -218,7 +217,6 @@ describe("UserModel", () => {
218
217
  profile: true,
219
218
  },
220
219
  null,
221
- null,
222
220
  true,
223
221
  );
224
222
  });
@@ -431,7 +429,7 @@ describe("UserModel", () => {
431
429
 
432
430
  const apiClientOption = defaultApiClientOptions();
433
431
  const model = new UserModel(apiClientOption);
434
- const collection = await model.findAll({}, {}, {}, true);
432
+ const collection = await model.findAll({}, {}, true);
435
433
 
436
434
  expect(collection).toHaveLength(3);
437
435
  expect(collection.items[0]._props.id).toEqual(dto1.id);
@@ -13,6 +13,9 @@
13
13
  import { ApiClient } from "passbolt-styleguide/src/shared/lib/apiClient/apiClient";
14
14
  import Validator from "validator";
15
15
 
16
+ /**
17
+ * @deprecated: to be replaced with AbstractService from the styleguide
18
+ */
16
19
  class AbstractService {
17
20
  /**
18
21
  *
@@ -75,23 +78,6 @@ class AbstractService {
75
78
  return result;
76
79
  }
77
80
 
78
- /**
79
- * Format contain orders
80
- *
81
- * @param {object} orders example: {"orders": ['Resources.name ASC']}
82
- * @param {array} supportedOrders example: ['Resources.name ASC', 'Resources.name DESC']
83
- * @returns {object} to be used in API request
84
- */
85
- formatOrderOptions(orders, supportedOrders) {
86
- const result = {};
87
- for (const order in orders) {
88
- if (supportedOrders.includes(order)) {
89
- result[`order[]`] = order;
90
- }
91
- }
92
- return result;
93
- }
94
-
95
81
  /**
96
82
  * Assert that an id is a valid uuid or throw a TypeError
97
83
  *
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Passbolt ~ Open source password manager for teams
3
+ * Copyright (c) Passbolt SA (https://www.passbolt.com)
4
+ *
5
+ * Licensed under GNU Affero General Public License version 3 of the or any later version.
6
+ * For full copyright and license information, please see the LICENSE.txt
7
+ * Redistributions of files must retain the above copyright notice.
8
+ *
9
+ * @copyright Copyright (c) Passbolt SA (https://www.passbolt.com)
10
+ * @license https://opensource.org/licenses/AGPL-3.0 AGPL License
11
+ * @link https://www.passbolt.com Passbolt(tm)
12
+ * @since 5.13.0
13
+ */
14
+
15
+ import AbstractService from "../abstract/abstractService";
16
+
17
+ export const PASSBOLT_EDITION_API_SERVICE_RESOURCE_NAME = "edition/subscription/key";
18
+
19
+ class PassboltEditionApiService extends AbstractService {
20
+ /**
21
+ * Constructor
22
+ *
23
+ * @param {ApiClientOptions} apiClientOptions
24
+ * @public
25
+ */
26
+ constructor(apiClientOptions) {
27
+ super(apiClientOptions, PassboltEditionApiService.RESOURCE_NAME);
28
+ }
29
+
30
+ /**
31
+ * API Resource Name
32
+ *
33
+ * @returns {string}
34
+ * @public
35
+ */
36
+ static get RESOURCE_NAME() {
37
+ return PASSBOLT_EDITION_API_SERVICE_RESOURCE_NAME;
38
+ }
39
+
40
+ /**
41
+ * Create the subscription on the API (upgrade CE to PRO).
42
+ *
43
+ * @param {Object} keyDto the new subscription key dto
44
+ * @throws {Error} if API call fails, service unreachable, etc.
45
+ * @returns {Promise<Object>} subscriptionDto
46
+ */
47
+ async create(keyDto) {
48
+ const response = await this.apiClient.create(keyDto);
49
+ return response.body;
50
+ }
51
+
52
+ /**
53
+ * Delete the subscription on the API (downgrade PRO to CE).
54
+ *
55
+ * @throws {Error} if API call fails, service unreachable, etc.
56
+ * @returns {Promise<void>}
57
+ */
58
+ async delete() {
59
+ const url = this.apiClient.buildUrl(this.apiClient.baseUrl.toString());
60
+ await this.apiClient.fetchAndHandleResponse("DELETE", url);
61
+ }
62
+ }
63
+
64
+ export default PassboltEditionApiService;
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Passbolt ~ Open source password manager for teams
3
+ * Copyright (c) Passbolt SA (https://www.passbolt.com)
4
+ *
5
+ * Licensed under GNU Affero General Public License version 3 of the or any later version.
6
+ * For full copyright and license information, please see the LICENSE.txt
7
+ * Redistributions of files must retain the above copyright notice.
8
+ *
9
+ * @copyright Copyright (c) Passbolt SA (https://www.passbolt.com)
10
+ * @license https://opensource.org/licenses/AGPL-3.0 AGPL License
11
+ * @link https://www.passbolt.com Passbolt(tm)
12
+ * @since 5.13.0
13
+ */
14
+
15
+ import { enableFetchMocks } from "jest-fetch-mock";
16
+
17
+ import PassboltBadResponseError from "passbolt-styleguide/src/shared/lib/Error/PassboltBadResponseError";
18
+ import SubscriptionEntity from "passbolt-styleguide/src/shared/models/entity/subscription/subscriptionEntity";
19
+ import { defaultApiClientOptions } from "passbolt-styleguide/src/shared/lib/apiClient/apiClientOptions.test.data";
20
+ import { mockSubscriptionUpdated } from "passbolt-styleguide/src/react-extension/components/Administration/DisplaySubscriptionKey/DisplaySubscriptionKey.test.data";
21
+
22
+ import { mockApiResponse } from "../../../../../../test/mocks/mockApiResponse";
23
+ import PassboltEditionApiService, { PASSBOLT_EDITION_API_SERVICE_RESOURCE_NAME } from "./passboltEditionApiService";
24
+
25
+ describe("PassboltEditionApiService", () => {
26
+ /**
27
+ * @type {PassboltEditionApiService}
28
+ */
29
+ let service;
30
+
31
+ beforeEach(() => {
32
+ enableFetchMocks();
33
+ fetch.resetMocks();
34
+ service = new PassboltEditionApiService(defaultApiClientOptions());
35
+ });
36
+
37
+ it("Should return the expected resource name", () => {
38
+ expect(PassboltEditionApiService.RESOURCE_NAME).toEqual(PASSBOLT_EDITION_API_SERVICE_RESOURCE_NAME);
39
+ });
40
+
41
+ describe("::create", () => {
42
+ it("Should POST the subscription key and return the response", async () => {
43
+ expect.assertions(4);
44
+
45
+ const subscriptionEntity = new SubscriptionEntity(mockSubscriptionUpdated);
46
+
47
+ fetch.doMockOnceIf(new RegExp(`/${PASSBOLT_EDITION_API_SERVICE_RESOURCE_NAME}\\.json`), async () =>
48
+ mockApiResponse(subscriptionEntity),
49
+ );
50
+
51
+ const result = await service.create({ data: mockSubscriptionUpdated.data });
52
+
53
+ expect(fetch).toHaveBeenCalledTimes(1);
54
+
55
+ const { method, body } = fetch.mock.calls[0][1];
56
+ expect(method).toEqual("POST");
57
+ expect(JSON.parse(body).data).toEqual(mockSubscriptionUpdated.data);
58
+
59
+ expect(result).toEqual(subscriptionEntity.toDto());
60
+ });
61
+
62
+ it("Should throw an error if the response is not properly formatted", async () => {
63
+ expect.assertions(2);
64
+
65
+ fetch.doMockOnceIf(new RegExp(`/${PASSBOLT_EDITION_API_SERVICE_RESOURCE_NAME}\\.json`), async () => "wrong");
66
+
67
+ await expect(service.create({ data: mockSubscriptionUpdated.data })).rejects.toBeInstanceOf(
68
+ PassboltBadResponseError,
69
+ );
70
+
71
+ expect(fetch).toHaveBeenCalledTimes(1);
72
+ });
73
+ });
74
+
75
+ describe("::delete", () => {
76
+ it("Should DELETE the subscription key and resolve", async () => {
77
+ expect.assertions(3);
78
+
79
+ fetch.doMockOnceIf(new RegExp(`/${PASSBOLT_EDITION_API_SERVICE_RESOURCE_NAME}\\.json`), async () =>
80
+ mockApiResponse({}),
81
+ );
82
+
83
+ await expect(service.delete()).resolves.not.toThrow();
84
+
85
+ expect(fetch).toHaveBeenCalledTimes(1);
86
+ expect(fetch.mock.calls[0][1].method).toEqual("DELETE");
87
+ });
88
+
89
+ it("Should throw an error if the response is not properly formatted", async () => {
90
+ expect.assertions(2);
91
+
92
+ fetch.doMockOnceIf(new RegExp(`/${PASSBOLT_EDITION_API_SERVICE_RESOURCE_NAME}\\.json`), async () => "wrong");
93
+
94
+ await expect(service.delete()).rejects.toBeInstanceOf(PassboltBadResponseError);
95
+
96
+ expect(fetch).toHaveBeenCalledTimes(1);
97
+ });
98
+ });
99
+ });