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
@@ -0,0 +1,65 @@
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 FindAndUpdateGroupsLocalStorageService from "./findAndUpdateGroupsLocalStorageService";
15
+ import GroupLocalStorage from "../local_storage/groupLocalStorage";
16
+ import GroupsCollection from "passbolt-styleguide/src/shared/models/entity/group/groupsCollection";
17
+ import { assertArrayUUID } from "../../utils/assertions";
18
+
19
+ /**
20
+ * The service aims to get groups from the local storage if it is set, or retrieve them from the API and
21
+ * set the local storage.
22
+ */
23
+ export default class GetOrFindGroupsService {
24
+ /**
25
+ *
26
+ * @param {AccountEntity} account The user account
27
+ * @param {ApiClientOptions} apiClientOptions The api client options
28
+ */
29
+ constructor(account, apiClientOptions) {
30
+ this.account = account;
31
+ this.groupLocalStorage = new GroupLocalStorage(account);
32
+ this.findAndUpdateGroupsLocalStorage = new FindAndUpdateGroupsLocalStorageService(account, apiClientOptions);
33
+ }
34
+
35
+ /**
36
+ * Get or find all groups.
37
+ * @returns {Promise<GroupsCollection>}
38
+ */
39
+ async getOrFindAll() {
40
+ const hasRuntimeCache = GroupLocalStorage.hasCachedData(this.account.id);
41
+ const groupsDto = await this.groupLocalStorage.get();
42
+ // Return local storage data if the storage was initialized.
43
+ if (groupsDto) {
44
+ // No validation is required if the data is in the runtime cache, as validation was done by the process that set the cache.
45
+ return new GroupsCollection(groupsDto, { validate: !hasRuntimeCache });
46
+ }
47
+
48
+ // Otherwise retrieve the groups and update the local storage.
49
+ return this.findAndUpdateGroupsLocalStorage.findAndUpdateAll();
50
+ }
51
+
52
+ /**
53
+ * Get or find all the groups matching the given ids.
54
+ * @param {Array<string>} groupIds The groups to find.
55
+ * @return {Promise<GroupsCollection>}
56
+ */
57
+ async getOrFindByIds(groupIds) {
58
+ assertArrayUUID(groupIds);
59
+
60
+ const groupsCollection = await this.getOrFindAll();
61
+ groupsCollection.filterByPropertyValueIn("id", groupIds);
62
+
63
+ return groupsCollection;
64
+ }
65
+ }
@@ -0,0 +1,168 @@
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 { v4 as uuidv4 } from "uuid";
16
+ import AccountEntity from "../../model/entity/account/accountEntity";
17
+ import { defaultAccountDto } from "../../model/entity/account/accountEntity.test.data";
18
+ import { defaultApiClientOptions } from "passbolt-styleguide/src/shared/lib/apiClient/apiClientOptions.test.data";
19
+ import GetOrFindGroupsService from "./getOrFindGroupsService";
20
+ import FindAndUpdateGroupsLocalStorageService from "./findAndUpdateGroupsLocalStorageService";
21
+ import FindGroupsService from "./findGroupsService";
22
+ import GroupsCollection from "passbolt-styleguide/src/shared/models/entity/group/groupsCollection";
23
+ import GroupLocalStorage from "../local_storage/groupLocalStorage";
24
+ import { defaultGroupsDtos } from "passbolt-styleguide/src/shared/models/entity/group/groupsCollection.test.data";
25
+
26
+ describe("GetOrFindGroupsService", () => {
27
+ let service, account;
28
+
29
+ beforeEach(async () => {
30
+ jest.clearAllMocks();
31
+ account = new AccountEntity(defaultAccountDto());
32
+ service = new GetOrFindGroupsService(account, defaultApiClientOptions());
33
+ // flush account related storage before each.
34
+ await service.groupLocalStorage.flush();
35
+ });
36
+
37
+ describe("::getOrFindAll", () => {
38
+ it("fetches from the API and initializes the local storage with an empty array when the API returns no groups.", async () => {
39
+ expect.assertions(4);
40
+ jest
41
+ .spyOn(FindGroupsService.prototype, "findAllForLocalStorage")
42
+ .mockImplementation(() => new GroupsCollection([]));
43
+ jest.spyOn(FindAndUpdateGroupsLocalStorageService.prototype, "findAndUpdateAll");
44
+
45
+ const groups = await service.getOrFindAll();
46
+
47
+ expect(FindAndUpdateGroupsLocalStorageService.prototype.findAndUpdateAll).toHaveBeenCalledTimes(1);
48
+ expect(groups).toBeInstanceOf(GroupsCollection);
49
+ expect(groups).toHaveLength(0);
50
+ expect(await service.groupLocalStorage.get()).toEqual([]);
51
+ });
52
+
53
+ it("fetches the groups from the API and populates the local storage when the local storage is not initialized.", async () => {
54
+ expect.assertions(4);
55
+ const groupsDto = defaultGroupsDtos();
56
+ jest
57
+ .spyOn(FindGroupsService.prototype, "findAllForLocalStorage")
58
+ .mockImplementation(() => new GroupsCollection(groupsDto));
59
+
60
+ const groups = await service.getOrFindAll();
61
+
62
+ expect(groups).toHaveLength(groupsDto.length);
63
+ expect(groups.toDto(GroupLocalStorage.DEFAULT_CONTAIN)).toEqual(groupsDto);
64
+ expect(GroupLocalStorage.hasCachedData(account.id)).toBeTruthy();
65
+ expect(await service.groupLocalStorage.get()).toEqual(groupsDto);
66
+ });
67
+
68
+ it("retrieves groups from the local storage when the local storage is initialized.", async () => {
69
+ expect.assertions(5);
70
+ const groupsDto = defaultGroupsDtos();
71
+ jest.spyOn(FindGroupsService.prototype, "findAllForLocalStorage");
72
+ await service.groupLocalStorage.set(new GroupsCollection(groupsDto));
73
+
74
+ const groups = await service.getOrFindAll();
75
+
76
+ expect(FindGroupsService.prototype.findAllForLocalStorage).not.toHaveBeenCalled();
77
+ expect(groups).toHaveLength(groupsDto.length);
78
+ expect(groups.toDto(GroupLocalStorage.DEFAULT_CONTAIN)).toEqual(groupsDto);
79
+ expect(GroupLocalStorage.hasCachedData(account.id)).toBeTruthy();
80
+ expect(await service.groupLocalStorage.get()).toEqual(groupsDto);
81
+ });
82
+
83
+ it("does not validate the groups collection if the information is retrieved from the runtime cache.", async () => {
84
+ expect.assertions(2);
85
+ jest.spyOn(FindGroupsService.prototype, "findAllForLocalStorage");
86
+ await service.groupLocalStorage.set(new GroupsCollection(defaultGroupsDtos()));
87
+ jest.spyOn(GroupsCollection.prototype, "validateSchema");
88
+
89
+ await service.getOrFindAll();
90
+
91
+ expect(FindGroupsService.prototype.findAllForLocalStorage).not.toHaveBeenCalled();
92
+ // Validation must not be triggered by getOrFindAll when the data comes from the runtime cache.
93
+ expect(GroupsCollection.prototype.validateSchema).not.toHaveBeenCalled();
94
+ });
95
+
96
+ it("validates groups collection if the local storage has no runtime cache and the information is retrieved from the local storage.", async () => {
97
+ expect.assertions(2);
98
+ jest.spyOn(FindGroupsService.prototype, "findAllForLocalStorage");
99
+ await service.groupLocalStorage.set(new GroupsCollection(defaultGroupsDtos()));
100
+ delete GroupLocalStorage._runtimeCachedData[account.id];
101
+ jest.spyOn(GroupsCollection.prototype, "validateSchema");
102
+
103
+ await service.getOrFindAll();
104
+
105
+ expect(FindGroupsService.prototype.findAllForLocalStorage).not.toHaveBeenCalled();
106
+ // Validation must be triggered by getOrFindAll when the data is loaded from the disk cache.
107
+ expect(GroupsCollection.prototype.validateSchema).toHaveBeenCalled();
108
+ });
109
+ });
110
+
111
+ describe("::getOrFindByIds", () => {
112
+ it("returns only the groups whose ids are in the requested set.", async () => {
113
+ expect.assertions(2);
114
+ const groupsDto = defaultGroupsDtos();
115
+ await service.groupLocalStorage.set(new GroupsCollection(groupsDto));
116
+ const requestedIds = [groupsDto[1].id, groupsDto[3].id, groupsDto[5].id];
117
+
118
+ const groups = await service.getOrFindByIds(requestedIds);
119
+
120
+ expect(groups).toHaveLength(3);
121
+ expect(groups.extract("id").sort()).toEqual([...requestedIds].sort());
122
+ });
123
+
124
+ it("returns an empty collection when none of the ids match.", async () => {
125
+ expect.assertions(1);
126
+ const groupsDto = defaultGroupsDtos();
127
+ await service.groupLocalStorage.set(new GroupsCollection(groupsDto));
128
+
129
+ const groups = await service.getOrFindByIds([uuidv4(), uuidv4()]);
130
+
131
+ expect(groups).toHaveLength(0);
132
+ });
133
+
134
+ it("returns all groups when every group id is requested.", async () => {
135
+ expect.assertions(2);
136
+ const groupsDto = defaultGroupsDtos();
137
+ await service.groupLocalStorage.set(new GroupsCollection(groupsDto));
138
+ const allIds = groupsDto.map((group) => group.id);
139
+
140
+ const groups = await service.getOrFindByIds(allIds);
141
+
142
+ expect(groups).toHaveLength(groupsDto.length);
143
+ expect(groups.extract("id").sort()).toEqual([...allIds].sort());
144
+ });
145
+
146
+ it("fetches from the API when the local storage is not initialized, then filters.", async () => {
147
+ expect.assertions(3);
148
+ const groupsDto = defaultGroupsDtos();
149
+ jest
150
+ .spyOn(FindGroupsService.prototype, "findAllForLocalStorage")
151
+ .mockImplementation(() => new GroupsCollection(groupsDto));
152
+ const requestedIds = [groupsDto[0].id, groupsDto[2].id];
153
+
154
+ const groups = await service.getOrFindByIds(requestedIds);
155
+
156
+ expect(FindGroupsService.prototype.findAllForLocalStorage).toHaveBeenCalledTimes(1);
157
+ expect(groups).toHaveLength(2);
158
+ expect(groups.extract("id").sort()).toEqual([...requestedIds].sort());
159
+ });
160
+
161
+ it("should assert its parameter.", async () => {
162
+ expect.assertions(1);
163
+ await expect(() => service.getOrFindByIds(["not-a-uuid"])).rejects.toThrow(
164
+ "The given parameter is not a valid array of uuid",
165
+ );
166
+ });
167
+ });
168
+ });
@@ -0,0 +1,51 @@
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 GroupsUsersCollection from "passbolt-styleguide/src/shared/models/entity/groupUser/groupsUsersCollection";
15
+ import GetOrFindGroupsService from "./getOrFindGroupsService";
16
+ import { assertUuid } from "../../utils/assertions";
17
+
18
+ /**
19
+ * The service aims to retrieve the group members of a given group from the local storage cache
20
+ * served by `GetOrFindGroupsService`, falling back to the API on a cold cache.
21
+ */
22
+ class GetOrFindGroupsUsersService {
23
+ /**
24
+ * Constructor.
25
+ * @param {AccountEntity} account The user account.
26
+ * @param {ApiClientOptions} apiClientOptions The api client options.
27
+ */
28
+ constructor(account, apiClientOptions) {
29
+ this.getOrFindGroupsService = new GetOrFindGroupsService(account, apiClientOptions);
30
+ }
31
+
32
+ /**
33
+ * Get or find the members of the group matching the given id.
34
+ * @param {string} groupId The id of the group whose members are requested.
35
+ * @returns {Promise<GroupsUsersCollection>}
36
+ * @throws {Error} If no group is found for the given id.
37
+ */
38
+ async getOrFindByGroupId(groupId) {
39
+ assertUuid(groupId);
40
+
41
+ const groupsCollection = await this.getOrFindGroupsService.getOrFindAll();
42
+ const group = groupsCollection.getFirst("id", groupId);
43
+ if (!group) {
44
+ throw new Error(`The group with id ${groupId} could not be found.`);
45
+ }
46
+
47
+ return group.groupsUsers ?? new GroupsUsersCollection([]);
48
+ }
49
+ }
50
+
51
+ export default GetOrFindGroupsUsersService;
@@ -0,0 +1,94 @@
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 { v4 as uuidv4 } from "uuid";
16
+ import AccountEntity from "../../model/entity/account/accountEntity";
17
+ import { defaultAccountDto } from "../../model/entity/account/accountEntity.test.data";
18
+ import { defaultApiClientOptions } from "passbolt-styleguide/src/shared/lib/apiClient/apiClientOptions.test.data";
19
+ import GetOrFindGroupsUsersService from "./getOrFindGroupsUsersService";
20
+ import FindGroupsService from "./findGroupsService";
21
+ import GroupsCollection from "passbolt-styleguide/src/shared/models/entity/group/groupsCollection";
22
+ import GroupsUsersCollection from "passbolt-styleguide/src/shared/models/entity/groupUser/groupsUsersCollection";
23
+ import { defaultGroupsDtos } from "passbolt-styleguide/src/shared/models/entity/group/groupsCollection.test.data";
24
+
25
+ describe("GetOrFindGroupsUsersService", () => {
26
+ let service, account;
27
+
28
+ beforeEach(async () => {
29
+ jest.clearAllMocks();
30
+ account = new AccountEntity(defaultAccountDto());
31
+ service = new GetOrFindGroupsUsersService(account, defaultApiClientOptions());
32
+ // flush account related storage before each.
33
+ await service.getOrFindGroupsService.groupLocalStorage.flush();
34
+ });
35
+
36
+ describe("::getOrFindByGroupId", () => {
37
+ it("returns the members of the requested group when the local storage is populated.", async () => {
38
+ expect.assertions(3);
39
+ const groupsDto = defaultGroupsDtos(5, { withGroupsUsers: 3 });
40
+ await service.getOrFindGroupsService.groupLocalStorage.set(new GroupsCollection(groupsDto));
41
+ const targetGroup = groupsDto[2];
42
+
43
+ const result = await service.getOrFindByGroupId(targetGroup.id);
44
+
45
+ expect(result).toBeInstanceOf(GroupsUsersCollection);
46
+ expect(result).toHaveLength(3);
47
+ expect(result.items.every((groupUser) => groupUser.groupId === targetGroup.id)).toBe(true);
48
+ });
49
+
50
+ it("returns an empty GroupsUsersCollection when the group has no members.", async () => {
51
+ expect.assertions(2);
52
+ const groupsDto = defaultGroupsDtos(1, { withGroupsUsers: 0 });
53
+ await service.getOrFindGroupsService.groupLocalStorage.set(new GroupsCollection(groupsDto));
54
+
55
+ const result = await service.getOrFindByGroupId(groupsDto[0].id);
56
+
57
+ expect(result).toBeInstanceOf(GroupsUsersCollection);
58
+ expect(result).toHaveLength(0);
59
+ });
60
+
61
+ it("fetches from the API when the local storage is not initialized, then returns the requested group's members.", async () => {
62
+ expect.assertions(3);
63
+ const groupsDto = defaultGroupsDtos(3, { withGroupsUsers: 2 });
64
+ jest
65
+ .spyOn(FindGroupsService.prototype, "findAllForLocalStorage")
66
+ .mockImplementation(() => new GroupsCollection(groupsDto));
67
+ const targetGroup = groupsDto[1];
68
+
69
+ const result = await service.getOrFindByGroupId(targetGroup.id);
70
+
71
+ expect(FindGroupsService.prototype.findAllForLocalStorage).toHaveBeenCalledTimes(1);
72
+ expect(result).toBeInstanceOf(GroupsUsersCollection);
73
+ expect(result).toHaveLength(2);
74
+ });
75
+
76
+ it("throws when the group id does not exist.", async () => {
77
+ expect.assertions(1);
78
+ const groupsDto = defaultGroupsDtos();
79
+ await service.getOrFindGroupsService.groupLocalStorage.set(new GroupsCollection(groupsDto));
80
+ const missingId = uuidv4();
81
+
82
+ await expect(() => service.getOrFindByGroupId(missingId)).rejects.toThrow(
83
+ `The group with id ${missingId} could not be found.`,
84
+ );
85
+ });
86
+
87
+ it("should assert its parameter.", async () => {
88
+ expect.assertions(1);
89
+ await expect(() => service.getOrFindByGroupId("not-a-uuid")).rejects.toThrow(
90
+ "The given parameter is not a valid UUID",
91
+ );
92
+ });
93
+ });
94
+ });
@@ -16,7 +16,7 @@ import Keyring from "../../model/keyring";
16
16
  import EncryptMessageService from "../../service/crypto/encryptMessageService";
17
17
  import DecryptMessageService from "../../service/crypto/decryptMessageService";
18
18
  import GroupModel from "../../model/group/groupModel";
19
- import GroupEntity from "../../model/entity/group/groupEntity";
19
+ import GroupEntity from "passbolt-styleguide/src/shared/models/entity/group/groupEntity";
20
20
  import GroupUpdateEntity from "../../model/entity/group/update/groupUpdateEntity";
21
21
  import i18n from "../../sdk/i18n";
22
22
  import SecretEntity from "passbolt-styleguide/src/shared/models/entity/secret/secretEntity";
@@ -37,7 +37,7 @@ import GroupApiService from "../api/group/groupApiService";
37
37
  * - Done
38
38
  */
39
39
  const PROGRESS_GOAL = 6;
40
- const YIELD_INTERVAL_MS = 25000; // yield avant 30s pour avoir une marge
40
+ const YIELD_INTERVAL_MS = 8000; // PB-51648 yield reduce to 8s due to chrome 148 more strict to ping service worker
41
41
 
42
42
  class GroupUpdateService {
43
43
  /**
@@ -216,7 +216,9 @@ class GroupUpdateService {
216
216
 
217
217
  this.progressService.updateStepMessage(progressMessage);
218
218
  const groupUpdateOperation = groupUpdateSingleOperationList.items[i];
219
- const groupDto = await this.groupApiService.update(groupUpdateOperation.id, groupUpdateOperation.toDto());
219
+ const groupDto = await this.groupApiService.update(groupUpdateOperation.id, groupUpdateOperation.toDto(), {
220
+ my_group_user: true,
221
+ });
220
222
  const updatedGroupEntity = new GroupEntity(groupDto, { ignoreInvalidEntity: true });
221
223
  await this.groupLocalStorage.updateGroup(updatedGroupEntity);
222
224
  }
@@ -12,7 +12,7 @@
12
12
  * @since 4.10.1
13
13
  */
14
14
  import GroupUpdateService from "./groupUpdateService";
15
- import GroupEntity from "../../model/entity/group/groupEntity";
15
+ import GroupEntity from "passbolt-styleguide/src/shared/models/entity/group/groupEntity";
16
16
  import GroupUpdateEntity from "../../model/entity/group/update/groupUpdateEntity";
17
17
  import GroupUpdateDryRunResultEntity from "../../model/entity/group/update/groupUpdateDryRunResultEntity";
18
18
  import EncryptMessageService from "../crypto/encryptMessageService";
@@ -89,7 +89,11 @@ describe("GroupUpdateService", () => {
89
89
  expect(spyOnGroupApiServiceUpdate).toHaveBeenCalledTimes(1);
90
90
  const expectedDiffGropuUpdateEntityDto = diffGroupUpdateEntity.toDto();
91
91
  delete expectedDiffGropuUpdateEntityDto.groups_users;
92
- expect(spyOnGroupApiServiceUpdate).toHaveBeenCalledWith(diffGroupUpdateEntity.id, expectedDiffGropuUpdateEntityDto);
92
+ expect(spyOnGroupApiServiceUpdate).toHaveBeenCalledWith(
93
+ diffGroupUpdateEntity.id,
94
+ expectedDiffGropuUpdateEntityDto,
95
+ { my_group_user: true },
96
+ );
93
97
 
94
98
  //Exepctation for the progressSerivce when there is no crypto involved
95
99
  expect(progressService.start).toHaveBeenCalledTimes(1);
@@ -171,10 +175,12 @@ describe("GroupUpdateService", () => {
171
175
  expect(spyOnGroupApiServiceUpdate).toHaveBeenCalledWith(
172
176
  diffGroupUpdateEntity.id,
173
177
  expectedDiffGropuUpdateEntityDto1,
178
+ { my_group_user: true },
174
179
  );
175
180
  expect(spyOnGroupApiServiceUpdate).toHaveBeenCalledWith(
176
181
  diffGroupUpdateEntity.id,
177
182
  expectedDiffGropuUpdateEntityDto2,
183
+ { my_group_user: true },
178
184
  );
179
185
  });
180
186
 
@@ -243,10 +249,12 @@ describe("GroupUpdateService", () => {
243
249
  expect(spyOnGroupApiServiceUpdate).toHaveBeenCalledWith(
244
250
  diffGroupUpdateEntity.id,
245
251
  expectedDiffGropuUpdateEntityDto1,
252
+ { my_group_user: true },
246
253
  );
247
254
  expect(spyOnGroupApiServiceUpdate).toHaveBeenCalledWith(
248
255
  diffGroupUpdateEntity.id,
249
256
  expectedDiffGropuUpdateEntityDto2,
257
+ { my_group_user: true },
250
258
  );
251
259
  });
252
260
 
@@ -12,8 +12,8 @@
12
12
  * @since 2.13.0
13
13
  */
14
14
  import Log from "../../model/log";
15
- import GroupsCollection from "../../model/entity/group/groupsCollection";
16
- import GroupEntity from "../../model/entity/group/groupEntity";
15
+ import GroupsCollection from "passbolt-styleguide/src/shared/models/entity/group/groupsCollection";
16
+ import GroupEntity from "passbolt-styleguide/src/shared/models/entity/group/groupEntity";
17
17
  import AccountEntity from "../../model/entity/account/accountEntity";
18
18
 
19
19
  export const GROUP_LOCAL_STORAGE_KEY = "groups";
@@ -14,10 +14,10 @@
14
14
  import { defaultGroupDto } from "passbolt-styleguide/src/shared/models/entity/group/groupEntity.test.data";
15
15
  import AccountEntity from "../../model/entity/account/accountEntity";
16
16
  import { defaultAccountDto } from "../../model/entity/account/accountEntity.test.data";
17
- import GroupsCollection from "../../model/entity/group/groupsCollection";
18
- import { defaultGroupsDtos } from "../../model/entity/group/groupsCollection.test.data";
17
+ import GroupsCollection from "passbolt-styleguide/src/shared/models/entity/group/groupsCollection";
18
+ import { defaultGroupsDtos } from "passbolt-styleguide/src/shared/models/entity/group/groupsCollection.test.data";
19
19
  import GroupLocalStorage from "./groupLocalStorage";
20
- import GroupEntity from "../../model/entity/group/groupEntity";
20
+ import GroupEntity from "passbolt-styleguide/src/shared/models/entity/group/groupEntity";
21
21
 
22
22
  beforeEach(() => {
23
23
  jest.clearAllMocks();
@@ -12,14 +12,27 @@
12
12
  * @since 2.13.0
13
13
  */
14
14
  import Log from "../../model/log";
15
- import UserEntity from "../../model/entity/user/userEntity";
16
- import UsersCollection from "../../model/entity/user/usersCollection";
17
- import Lock from "../../utils/lock";
18
- const lock = new Lock();
15
+ import UserEntity from "passbolt-styleguide/src/shared/models/entity/user/userEntity";
16
+ import UsersCollection from "passbolt-styleguide/src/shared/models/entity/user/usersCollection";
19
17
 
20
18
  const USER_LOCAL_STORAGE_KEY = "users";
21
19
 
22
20
  class UserLocalStorage {
21
+ /**
22
+ * Runtime cached data.
23
+ * @type {Array|null}
24
+ * @private
25
+ */
26
+ static _runtimeCachedData = null;
27
+
28
+ /**
29
+ * Check if there is cached data.
30
+ * @returns {boolean}
31
+ */
32
+ static hasCachedData() {
33
+ return UserLocalStorage._runtimeCachedData !== null && UserLocalStorage._runtimeCachedData !== undefined;
34
+ }
35
+
23
36
  /**
24
37
  * Flush the users local storage
25
38
  *
@@ -27,40 +40,50 @@ class UserLocalStorage {
27
40
  * @return {Promise<void>}
28
41
  */
29
42
  static async flush() {
43
+ UserLocalStorage._runtimeCachedData = null;
30
44
  Log.write({ level: "debug", message: "UserLocalStorage flushed" });
31
45
  return await browser.storage.local.remove(UserLocalStorage.USER_LOCAL_STORAGE_KEY);
32
46
  }
33
47
 
34
48
  /**
35
- * Set the users local storage.
49
+ * Get the users from the local storage.
36
50
  *
37
51
  * @throws {Error} if operation failed
38
52
  * @return {Promise} results object, containing every object in keys that was found in the storage area.
39
53
  * If storage is not set, undefined will be returned.
40
54
  */
41
55
  static async get() {
56
+ if (UserLocalStorage._runtimeCachedData) {
57
+ return UserLocalStorage._runtimeCachedData;
58
+ }
42
59
  const { users } = await browser.storage.local.get([UserLocalStorage.USER_LOCAL_STORAGE_KEY]);
43
- return users;
60
+ if (!users) {
61
+ return undefined;
62
+ }
63
+ UserLocalStorage._runtimeCachedData = users;
64
+ return UserLocalStorage._runtimeCachedData;
44
65
  }
45
66
 
46
67
  /**
47
68
  * Set the users local storage.
48
69
  *
49
70
  * @param {UsersCollection} usersCollection The users to insert in the local storage.
50
- * @return {void}
71
+ * @return {Promise<void>}
51
72
  */
52
73
  static async set(usersCollection) {
53
- await lock.acquire();
54
- const users = [];
55
74
  if (!(usersCollection instanceof UsersCollection)) {
56
75
  throw new TypeError("UserLocalStorage::set expects a UsersCollection");
57
76
  }
58
- for (const userEntity of usersCollection) {
59
- UserLocalStorage.assertEntityBeforeSave(userEntity);
60
- users.push(userEntity.toDto(UserLocalStorage.DEFAULT_CONTAIN));
61
- }
62
- await browser.storage.local.set({ users: users });
63
- lock.release();
77
+
78
+ await navigator.locks.request(USER_LOCAL_STORAGE_KEY, async () => {
79
+ const users = [];
80
+ for (const userEntity of usersCollection) {
81
+ UserLocalStorage.assertEntityBeforeSave(userEntity);
82
+ users.push(userEntity.toDto(UserLocalStorage.DEFAULT_CONTAIN));
83
+ }
84
+ await browser.storage.local.set({ users: users });
85
+ UserLocalStorage._runtimeCachedData = users;
86
+ });
64
87
  }
65
88
 
66
89
  /**
@@ -77,30 +100,35 @@ class UserLocalStorage {
77
100
  /**
78
101
  * Add a user in the local storage
79
102
  * @param {UserEntity} userEntity
103
+ * @throws {TypeError} If parameter userEntity is not of type UserEntity.
80
104
  */
81
105
  static async addUser(userEntity) {
82
- await lock.acquire();
83
- try {
106
+ if (!userEntity || !(userEntity instanceof UserEntity)) {
107
+ throw new TypeError("UserLocalStorage expects an object of type UserEntity");
108
+ }
109
+
110
+ await navigator.locks.request(USER_LOCAL_STORAGE_KEY, async () => {
84
111
  UserLocalStorage.assertEntityBeforeSave(userEntity);
85
112
  const users = await UserLocalStorage.get();
86
113
  users.push(userEntity.toDto(UserLocalStorage.DEFAULT_CONTAIN));
87
114
  await browser.storage.local.set({ users: users });
88
- lock.release();
89
- } catch (error) {
90
- lock.release();
91
- throw error;
92
- }
115
+ UserLocalStorage._runtimeCachedData = users;
116
+ });
93
117
  }
94
118
 
95
119
  /**
96
120
  * Update a user in the local storage.
97
121
  *
98
122
  * @param {UserEntity} userEntity The user to update
123
+ * @throws {TypeError} If parameter userEntity is not of type UserEntity.
99
124
  * @throws {Error} if the user does not exist in the local storage
100
125
  */
101
126
  static async updateUser(userEntity) {
102
- await lock.acquire();
103
- try {
127
+ if (!userEntity || !(userEntity instanceof UserEntity)) {
128
+ throw new TypeError("UserLocalStorage expects an object of type UserEntity");
129
+ }
130
+
131
+ await navigator.locks.request(USER_LOCAL_STORAGE_KEY, async () => {
104
132
  UserLocalStorage.assertEntityBeforeSave(userEntity);
105
133
  const users = await UserLocalStorage.get();
106
134
  // If the local storage has been already initialized.
@@ -111,12 +139,9 @@ class UserLocalStorage {
111
139
  }
112
140
  users[userIndex] = Object.assign(users[userIndex], userEntity.toDto(UserLocalStorage.DEFAULT_CONTAIN));
113
141
  await browser.storage.local.set({ users: users });
142
+ UserLocalStorage._runtimeCachedData = users;
114
143
  }
115
- lock.release();
116
- } catch (error) {
117
- lock.release();
118
- throw error;
119
- }
144
+ });
120
145
  }
121
146
 
122
147
  /**
@@ -124,8 +149,7 @@ class UserLocalStorage {
124
149
  * @param {string} userId user uuid
125
150
  */
126
151
  static async delete(userId) {
127
- await lock.acquire();
128
- try {
152
+ await navigator.locks.request(USER_LOCAL_STORAGE_KEY, async () => {
129
153
  const users = await UserLocalStorage.get();
130
154
  if (users) {
131
155
  const userIndex = users.findIndex((item) => item.id === userId);
@@ -133,12 +157,9 @@ class UserLocalStorage {
133
157
  users.splice(userIndex, 1);
134
158
  }
135
159
  await browser.storage.local.set({ users: users });
136
- lock.release();
160
+ UserLocalStorage._runtimeCachedData = users;
137
161
  }
138
- } catch (error) {
139
- lock.release();
140
- throw error;
141
- }
162
+ });
142
163
  }
143
164
 
144
165
  /**