passbolt-browser-extension 5.3.2 → 5.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. package/CHANGELOG.md +58 -1
  2. package/README.md +2 -2
  3. package/RELEASE_NOTES.md +8 -30
  4. package/crowdin.yml +1 -0
  5. package/package.json +4 -3
  6. package/src/all/_locales/cs/messages.json +10 -0
  7. package/src/all/background_page/controller/InformMenuController/InformMenuController.js +3 -3
  8. package/src/all/background_page/controller/auth/redirectPostLoginController.js +57 -0
  9. package/src/all/background_page/controller/auth/redirectPostLoginController.test.js +82 -0
  10. package/src/all/background_page/controller/auth/redirectToAdminWorkspaceController.js +50 -0
  11. package/src/all/background_page/controller/auth/redirectToAdminWorkspaceController.test.js +45 -0
  12. package/src/all/background_page/controller/comment/createCommentController.js +3 -3
  13. package/src/all/background_page/controller/comment/createCommentController.test.js +5 -5
  14. package/src/all/background_page/controller/comment/deleteCommentController.js +3 -3
  15. package/src/all/background_page/controller/comment/deleteCommentController.test.js +3 -3
  16. package/src/all/background_page/controller/comment/getCommentsByRessourceIdController.js +3 -3
  17. package/src/all/background_page/controller/comment/getCommentsByRessourceidController.test.js +2 -2
  18. package/src/all/background_page/controller/import/importResourcesFileController.test.js +23 -23
  19. package/src/all/background_page/controller/metadata/enableEncryptedMetadataForExistingInstanceController.js +54 -0
  20. package/src/all/background_page/controller/metadata/enableEncryptedMetadataForExistingInstanceController.test.js +54 -0
  21. package/src/all/background_page/controller/metadata/enableMetadataSetupSettingsController.js +54 -0
  22. package/src/all/background_page/controller/metadata/enableMetadataSetupSettingsController.test.js +64 -0
  23. package/src/all/background_page/controller/metadata/findAllNonDeletedMetadataKeysController.js +2 -3
  24. package/src/all/background_page/controller/metadata/findMetadataGettingStartedSettingsController.js +50 -0
  25. package/src/all/background_page/controller/metadata/findMetadataGettingStartedSettingsController.test.js +33 -0
  26. package/src/all/background_page/controller/metadata/findMetadataSetupSettingsController.js +50 -0
  27. package/src/all/background_page/controller/metadata/findMetadataSetupSettingsController.test.js +42 -0
  28. package/src/all/background_page/controller/metadata/keepCleartextMetadataForExistingInstanceController.js +51 -0
  29. package/src/all/background_page/controller/metadata/keepCleartextMetadataForExistingInstanceController.test.js +47 -0
  30. package/src/all/background_page/controller/permission/FindAcoPermissionsForDisplayController.js +1 -0
  31. package/src/all/background_page/controller/quickaccess/consumeInProgressCreationResourceController.js +53 -0
  32. package/src/all/background_page/controller/quickaccess/consumeInProgressCreationResourceController.test.js +40 -0
  33. package/src/all/background_page/controller/quickaccess/prepareResourceController.js +64 -0
  34. package/src/all/background_page/controller/quickaccess/prepareResourceController.test.js +73 -0
  35. package/src/all/background_page/controller/resource/resourceDeleteController.js +67 -0
  36. package/src/all/background_page/controller/resource/resourceDeleteController.test.js +114 -0
  37. package/src/all/background_page/controller/resourceLocalStorage/resourceUpdateLocalStorageController.js +1 -1
  38. package/src/all/background_page/controller/resourceLocalStorage/resourceUpdateLocalStorageController.test.js +5 -1
  39. package/src/all/background_page/controller/setup/signInSetupController.js +0 -10
  40. package/src/all/background_page/controller/setup/signInSetupController.test.js +11 -12
  41. package/src/all/background_page/controller/sso/saveSsoSettingsAsDraftController.test.data.js +1 -0
  42. package/src/all/background_page/controller/webIntegration/webIntegrationController.js +1 -1
  43. package/src/all/background_page/event/appEvents.js +47 -0
  44. package/src/all/background_page/event/authEvents.js +4 -8
  45. package/src/all/background_page/event/informMenuEvents.js +31 -0
  46. package/src/all/background_page/event/quickAccessEvents.js +18 -23
  47. package/src/all/background_page/event/recoverEvents.js +12 -0
  48. package/src/all/background_page/event/resourceEvents.js +4 -11
  49. package/src/all/background_page/event/setupEvents.js +55 -0
  50. package/src/all/background_page/model/comment/{commentModel.js → commentService.js} +6 -2
  51. package/src/all/background_page/model/comment/commentService.test.js +98 -0
  52. package/src/all/background_page/model/entity/actionLog/actionLogsCollection.js +3 -3
  53. package/src/all/background_page/model/entity/actionLog/permissionsUpdatedActionLogEntity.js +4 -0
  54. package/src/all/background_page/model/entity/import/importResourcesFileEntity.test.data.js +3 -3
  55. package/src/all/background_page/model/entity/organizationSettings/organizationSettingsEntity.test.data.js +4 -0
  56. package/src/all/background_page/model/entity/permission/actionLog/updatedPermissionEntity.test.data.js +23 -0
  57. package/src/all/background_page/model/entity/permission/actionLog/updatedPermissionEntity.test.js +18 -33
  58. package/src/all/background_page/model/entity/permission/actionLog/updatedPermissionsCollection.js +71 -2
  59. package/src/all/background_page/model/entity/permission/actionLog/updatedPermissionsCollection.test.js +204 -0
  60. package/src/all/background_page/model/entity/permission/permissionsCollection.js +78 -0
  61. package/src/all/background_page/model/entity/permission/permissionsCollection.test.js +139 -7
  62. package/src/all/background_page/model/entity/plaintext/plaintextEntity.js +9 -0
  63. package/src/all/background_page/model/entity/resource/external/externalResourceEntity.js +65 -8
  64. package/src/all/background_page/model/entity/resource/external/externalResourceEntity.test.data.js +5 -4
  65. package/src/all/background_page/model/entity/resource/external/externalResourceEntity.test.js +72 -16
  66. package/src/all/background_page/model/entity/resource/external/externalResourcesCollection.test.js +2 -1
  67. package/src/all/background_page/model/entity/totp/externalTotpEntity.js +2 -2
  68. package/src/all/background_page/model/entity/totp/totpEntity.test.js +1 -1
  69. package/src/all/background_page/model/export/resources/csvRowComposer/csv1PasswordRowComposer.test.js +2 -2
  70. package/src/all/background_page/model/export/resources/csvRowComposer/csv1passwordRowComposer.js +5 -1
  71. package/src/all/background_page/model/export/resources/csvRowComposer/csvBitWardenRowComposer.js +9 -1
  72. package/src/all/background_page/model/export/resources/csvRowComposer/csvBitWardenRowComposer.test.js +6 -4
  73. package/src/all/background_page/model/export/resources/csvRowComposer/csvChromiumRowComposer.js +5 -1
  74. package/src/all/background_page/model/export/resources/csvRowComposer/csvChromiumRowComposer.test.js +3 -2
  75. package/src/all/background_page/model/export/resources/csvRowComposer/csvDashlaneRowComposer.js +5 -1
  76. package/src/all/background_page/model/export/resources/csvRowComposer/csvDashlaneRowComposer.test.js +2 -3
  77. package/src/all/background_page/model/export/resources/csvRowComposer/csvKdbxRowComposer.js +3 -1
  78. package/src/all/background_page/model/export/resources/csvRowComposer/csvKdbxRowComposer.test.js +6 -4
  79. package/src/all/background_page/model/export/resources/csvRowComposer/csvLastPassRowComposer.js +5 -1
  80. package/src/all/background_page/model/export/resources/csvRowComposer/csvLastPassRowComposer.test.js +2 -3
  81. package/src/all/background_page/model/export/resources/csvRowComposer/csvLogMeOnceRowComposer.js +5 -1
  82. package/src/all/background_page/model/export/resources/csvRowComposer/csvLogMeOnceRowComposer.test.js +2 -3
  83. package/src/all/background_page/model/export/resources/csvRowComposer/csvMozillaPlatformRowComposer.js +6 -2
  84. package/src/all/background_page/model/export/resources/csvRowComposer/csvMozillaPlatformRowComposer.test.js +2 -2
  85. package/src/all/background_page/model/export/resources/csvRowComposer/csvNordpassRowComposer.js +5 -1
  86. package/src/all/background_page/model/export/resources/csvRowComposer/csvNordpassRowComposer.test.js +2 -2
  87. package/src/all/background_page/model/export/resources/csvRowComposer/csvSafariRowComposer.js +5 -1
  88. package/src/all/background_page/model/export/resources/csvRowComposer/csvSafariRowComposer.test.js +2 -2
  89. package/src/all/background_page/model/export/resources/resourcesKdbxExporter.js +44 -2
  90. package/src/all/background_page/model/export/resources/resourcesKdbxExporter.test.js +24 -3
  91. package/src/all/background_page/model/import/resources/csvRowParser/abstractCsvRowParser.js +1 -1
  92. package/src/all/background_page/model/import/resources/csvRowParser/csv1PasswordRowParser.js +5 -3
  93. package/src/all/background_page/model/import/resources/csvRowParser/csv1PasswordRowParser.test.js +3 -2
  94. package/src/all/background_page/model/import/resources/csvRowParser/csvBitWardenRowParser.js +22 -3
  95. package/src/all/background_page/model/import/resources/csvRowParser/csvBitWardenRowParser.test.js +92 -4
  96. package/src/all/background_page/model/import/resources/csvRowParser/csvChromiumRowParser.js +4 -2
  97. package/src/all/background_page/model/import/resources/csvRowParser/csvChromiumRowParser.test.js +2 -2
  98. package/src/all/background_page/model/import/resources/csvRowParser/csvDashlaneRowParser.js +4 -2
  99. package/src/all/background_page/model/import/resources/csvRowParser/csvDashlaneRowParser.test.js +3 -2
  100. package/src/all/background_page/model/import/resources/csvRowParser/csvKdbxRowParser.js +5 -3
  101. package/src/all/background_page/model/import/resources/csvRowParser/csvKdbxRowParser.test.js +4 -4
  102. package/src/all/background_page/model/import/resources/csvRowParser/csvLastPassRowParser.js +4 -2
  103. package/src/all/background_page/model/import/resources/csvRowParser/csvLastPassRowParser.test.js +2 -2
  104. package/src/all/background_page/model/import/resources/csvRowParser/csvLogMeOnceRowParser.js +5 -2
  105. package/src/all/background_page/model/import/resources/csvRowParser/csvLogMeOnceRowParser.test.js +2 -2
  106. package/src/all/background_page/model/import/resources/csvRowParser/csvMozillaPlatformRowParser.js +5 -3
  107. package/src/all/background_page/model/import/resources/csvRowParser/csvMozillaPlatformRowParser.test.js +2 -2
  108. package/src/all/background_page/model/import/resources/csvRowParser/csvNordpassRowParser.js +4 -2
  109. package/src/all/background_page/model/import/resources/csvRowParser/csvNordpassRowParser.test.js +2 -2
  110. package/src/all/background_page/model/import/resources/csvRowParser/csvSafariRowParser.js +4 -2
  111. package/src/all/background_page/model/import/resources/csvRowParser/csvSafariRowParser.test.js +2 -2
  112. package/src/all/background_page/model/import/resources/kdbx/kdbx-custom-fields-with-uris.kdbx +0 -0
  113. package/src/all/background_page/model/import/resources/kdbx/kdbx-multiple-uris-with-33-entries.kdbx +0 -0
  114. package/src/all/background_page/model/import/resources/kdbx/kdbx-multiple-uris.kdbx +0 -0
  115. package/src/all/background_page/model/import/resources/kdbx/kdbx-with-protected-custom-fields.kdbx +0 -0
  116. package/src/all/background_page/model/import/resources/resourcesCsvImportParser.test.js +1 -1
  117. package/src/all/background_page/model/import/resources/resourcesKdbxImportParser.js +124 -41
  118. package/src/all/background_page/model/import/resources/resourcesKdbxImportParser.test.js +133 -1
  119. package/src/all/background_page/model/import/resourcesImportParser.test.js +0 -1
  120. package/src/all/background_page/model/resource/resourceModel.js +0 -68
  121. package/src/all/background_page/service/api/comment/commentApiService.test.js +1 -1
  122. package/src/all/background_page/service/api/metadata/metadataSetupSettingsApiService.js +40 -0
  123. package/src/all/background_page/service/api/metadata/metadataSetupSettingsApiService.test.js +54 -0
  124. package/src/all/background_page/service/api/setup/setupService.js +2 -2
  125. package/src/all/background_page/service/api/setup/setupService.test.js +132 -0
  126. package/src/all/background_page/service/local_storage/resourceLocalStorage.js +25 -1
  127. package/src/all/background_page/service/local_storage/resourceLocalStorage.test.js +54 -0
  128. package/src/all/background_page/service/metadata/configureMetadataSettingsService.js +100 -0
  129. package/src/all/background_page/service/metadata/configureMetadataSettingsService.test.js +265 -0
  130. package/src/all/background_page/service/metadata/createMetadataKeyService.js +1 -1
  131. package/src/all/background_page/service/metadata/decryptMetadataPrivateKeysService.js +3 -19
  132. package/src/all/background_page/service/metadata/decryptMetadataService.js +5 -3
  133. package/src/all/background_page/service/metadata/decryptMetadataService.test.js +31 -24
  134. package/src/all/background_page/service/metadata/encryptMetadataService.js +2 -18
  135. package/src/all/background_page/service/metadata/findAndUpdateMetadataKeysSessionStorageService.js +5 -2
  136. package/src/all/background_page/service/metadata/findAndUpdateMetadataKeysSessionStorageService.test.js +4 -6
  137. package/src/all/background_page/service/metadata/findMetadataKeysService.js +8 -12
  138. package/src/all/background_page/service/metadata/findMetadataKeysService.test.js +21 -47
  139. package/src/all/background_page/service/metadata/findMetadataSetupSettingsService.js +45 -0
  140. package/src/all/background_page/service/metadata/findMetadataSetupSettingsService.test.js +68 -0
  141. package/src/all/background_page/service/metadata/generateMetadataKeyService.js +1 -1
  142. package/src/all/background_page/service/metadata/verifyOrTrustMetadataKeyService.test.js +16 -0
  143. package/src/all/background_page/service/passphrase/getPassphraseService.js +13 -0
  144. package/src/all/background_page/service/permission/findPermissionsService.js +3 -1
  145. package/src/all/background_page/service/resource/delete/deleteResourceService.js +60 -0
  146. package/src/all/background_page/service/resource/delete/deleteResourceService.test.js +75 -0
  147. package/src/all/background_page/service/resource/export/exportResourcesService.js +22 -0
  148. package/src/all/background_page/service/resource/export/exportResourcesService.test.js +48 -1
  149. package/src/all/background_page/service/resource/import/ImportResourcesService.js +34 -3
  150. package/src/all/background_page/service/resource/import/ImportResourcesService.test.js +55 -13
  151. package/src/all/background_page/service/sessionKey/decryptSessionKeysBundlesService.js +2 -18
  152. package/src/all/background_page/service/sessionKey/encryptSessionKeysBundlesService.js +1 -17
  153. package/src/all/locales/cs-CZ/common.json +130 -0
  154. package/src/all/locales/de-DE/common.json +11 -5
  155. package/src/all/locales/en-UK/common.json +6 -0
  156. package/src/all/locales/es-ES/common.json +6 -0
  157. package/src/all/locales/fr-FR/common.json +6 -0
  158. package/src/all/locales/it-IT/common.json +6 -0
  159. package/src/all/locales/ja-JP/common.json +6 -0
  160. package/src/all/locales/ko-KR/common.json +6 -0
  161. package/src/all/locales/lt-LT/common.json +6 -0
  162. package/src/all/locales/nl-NL/common.json +6 -0
  163. package/src/all/locales/pl-PL/common.json +6 -0
  164. package/src/all/locales/pt-BR/common.json +6 -0
  165. package/src/all/locales/ro-RO/common.json +6 -0
  166. package/src/all/locales/ru-RU/common.json +6 -0
  167. package/src/all/locales/sl-SI/common.json +6 -0
  168. package/src/all/locales/sv-SE/common.json +6 -0
  169. package/src/all/locales/uk-UA/common.json +6 -0
  170. package/src/chrome/manifest.json +1 -1
  171. package/src/chrome-mv3/manifest.json +1 -1
  172. package/src/firefox/manifest.json +1 -1
  173. package/src/safari/manifest.json +1 -1
@@ -0,0 +1,204 @@
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.4.0
13
+ */
14
+ import EntitySchema from "passbolt-styleguide/src/shared/models/entity/abstract/entitySchema";
15
+ import UpdatedPermissionsCollection from "./updatedPermissionsCollection";
16
+ import {defaultUserDto} from "passbolt-styleguide/src/shared/models/entity/user/userEntity.test.data";
17
+ import {defaultGroupDto} from "passbolt-styleguide/src/shared/models/entity/group/groupEntity.test.data";
18
+ import {defaultUpdatePermissionDto} from "./updatedPermissionEntity.test.data";
19
+ import UpdatedPermissionEntity from "./updatedPermissionEntity";
20
+
21
+ describe("UpdatedPermissionsCollection", () => {
22
+ it("schema must validate", () => {
23
+ EntitySchema.validateSchema(UpdatedPermissionsCollection.ENTITY_NAME, UpdatedPermissionsCollection.getSchema());
24
+ });
25
+
26
+ describe("::constructor", () => {
27
+ it("works with empty data", () => {
28
+ expect.assertions(1);
29
+ const collection = new UpdatedPermissionsCollection([]);
30
+ expect(collection).toHaveLength(0);
31
+ });
32
+
33
+ it("works if valid DTOs are provided", () => {
34
+ expect.assertions(6);
35
+
36
+ const dto1 = defaultUpdatePermissionDto({user: defaultUserDto()});
37
+ const dto2 = defaultUpdatePermissionDto({group: defaultGroupDto()});
38
+ const dtos = [dto1, dto2];
39
+ const collection = new UpdatedPermissionsCollection(dtos);
40
+
41
+ expect(collection.items).toHaveLength(2);
42
+ expect(collection.toDto()).toEqual(dtos);
43
+
44
+ expect(collection.items[0]).toBeInstanceOf(UpdatedPermissionEntity);
45
+ expect(collection.items[0].toDto(UpdatedPermissionEntity.ALL_CONTAIN_OPTIONS)).toStrictEqual(dto1);
46
+ expect(collection.items[1]).toBeInstanceOf(UpdatedPermissionEntity);
47
+ expect(collection.items[1].toDto(UpdatedPermissionEntity.ALL_CONTAIN_OPTIONS)).toEqual(dto2);
48
+ });
49
+
50
+ it("should throw if the collection schema does not validate", () => {
51
+ expect.assertions(1);
52
+ expect(() => new UpdatedPermissionsCollection({}))
53
+ .toThrowEntityValidationError("items");
54
+ });
55
+
56
+ it("should throw if one of data item does not validate the collection entity schema", () => {
57
+ expect.assertions(1);
58
+
59
+ const dto1 = defaultUpdatePermissionDto();
60
+ const dto2 = defaultUpdatePermissionDto({user: 42});
61
+
62
+ expect(() => new UpdatedPermissionsCollection([dto1, dto2])).toThrow();
63
+ });
64
+
65
+ it("should throw if one of data item does not validate the unique id build rule", () => {
66
+ expect.assertions(1);
67
+
68
+ const dto1 = defaultUpdatePermissionDto();
69
+
70
+ expect(() => new UpdatedPermissionsCollection([dto1, dto1])).toThrow();
71
+ });
72
+ });
73
+
74
+ describe("::sortPermissionsByGranteeTypeAndName", () => {
75
+ it("should order users by their name", () => {
76
+ expect.assertions(3);
77
+
78
+ const userA = defaultUserDto();
79
+ userA.profile.first_name = "user A";
80
+ const userB = defaultUserDto();
81
+ userB.profile.first_name = "user B";
82
+
83
+ const permissionA = new UpdatedPermissionEntity(defaultUpdatePermissionDto({user: userA}));
84
+ const permissionB = new UpdatedPermissionEntity(defaultUpdatePermissionDto({user: userB}));
85
+
86
+ expect(UpdatedPermissionsCollection.sortPermissionsByGranteeTypeAndName(permissionA, permissionB)).toStrictEqual(-1);
87
+ expect(UpdatedPermissionsCollection.sortPermissionsByGranteeTypeAndName(permissionB, permissionA)).toStrictEqual(1);
88
+ expect(UpdatedPermissionsCollection.sortPermissionsByGranteeTypeAndName(permissionA, permissionA)).toStrictEqual(0);
89
+ });
90
+
91
+ it("should consider equal permisions with users having the same name", () => {
92
+ expect.assertions(1);
93
+
94
+ const userA = defaultUserDto();
95
+ userA.profile.first_name = "user";
96
+ const userB = defaultUserDto();
97
+ userB.profile.first_name = "user";
98
+
99
+ const permissionA = new UpdatedPermissionEntity(defaultUpdatePermissionDto({user: userA}));
100
+ const permissionB = new UpdatedPermissionEntity(defaultUpdatePermissionDto({user: userB}));
101
+
102
+ expect(UpdatedPermissionsCollection.sortPermissionsByGranteeTypeAndName(permissionA, permissionB)).toStrictEqual(0);
103
+ });
104
+
105
+ it("should put user without definition after users with definition", () => {
106
+ expect.assertions(2);
107
+
108
+ const userB = defaultUserDto();
109
+ userB.profile.first_name = "user B";
110
+
111
+ const permissionA = new UpdatedPermissionEntity(defaultUpdatePermissionDto({user: null}));
112
+ const permissionB = new UpdatedPermissionEntity(defaultUpdatePermissionDto({user: userB}));
113
+
114
+ expect(UpdatedPermissionsCollection.sortPermissionsByGranteeTypeAndName(permissionA, permissionB)).toStrictEqual(1);
115
+ expect(UpdatedPermissionsCollection.sortPermissionsByGranteeTypeAndName(permissionB, permissionA)).toStrictEqual(-1);
116
+ });
117
+
118
+ it("both undfined users should be considered equal", () => {
119
+ expect.assertions(1);
120
+
121
+ const permissionA = new UpdatedPermissionEntity(defaultUpdatePermissionDto({user: null}));
122
+ const permissionB = new UpdatedPermissionEntity(defaultUpdatePermissionDto({user: null}));
123
+
124
+ expect(UpdatedPermissionsCollection.sortPermissionsByGranteeTypeAndName(permissionA, permissionB)).toStrictEqual(0);
125
+ });
126
+
127
+ it("should order groups by their name", () => {
128
+ expect.assertions(3);
129
+
130
+ const groupA = defaultGroupDto({name: "Group A"});
131
+ const groupB = defaultGroupDto({name: "Group B"});
132
+
133
+ const permissionA = new UpdatedPermissionEntity(defaultUpdatePermissionDto({user: null, group: groupA}));
134
+ const permissionB = new UpdatedPermissionEntity(defaultUpdatePermissionDto({user: null, group: groupB}));
135
+
136
+ expect(UpdatedPermissionsCollection.sortPermissionsByGranteeTypeAndName(permissionA, permissionB)).toStrictEqual(-1);
137
+ expect(UpdatedPermissionsCollection.sortPermissionsByGranteeTypeAndName(permissionB, permissionA)).toStrictEqual(1);
138
+ expect(UpdatedPermissionsCollection.sortPermissionsByGranteeTypeAndName(permissionA, permissionA)).toStrictEqual(0);
139
+ });
140
+
141
+ it("should consider equal permisions with groups having the same name", () => {
142
+ expect.assertions(1);
143
+
144
+ const groupA = defaultGroupDto({name: "Group"});
145
+ const groupB = defaultGroupDto({name: "Group"});
146
+
147
+ const permissionA = new UpdatedPermissionEntity(defaultUpdatePermissionDto({user: null, group: groupA}));
148
+ const permissionB = new UpdatedPermissionEntity(defaultUpdatePermissionDto({user: null, group: groupB}));
149
+
150
+ expect(UpdatedPermissionsCollection.sortPermissionsByGranteeTypeAndName(permissionA, permissionB)).toStrictEqual(0);
151
+ });
152
+
153
+ it("should put group without definition after groups with definition", () => {
154
+ expect.assertions(2);
155
+
156
+ const groupB = defaultGroupDto({name: "Group B"});
157
+
158
+ const permissionA = new UpdatedPermissionEntity(defaultUpdatePermissionDto({user: null, group: null}));
159
+ const permissionB = new UpdatedPermissionEntity(defaultUpdatePermissionDto({user: null, group: groupB}));
160
+
161
+ expect(UpdatedPermissionsCollection.sortPermissionsByGranteeTypeAndName(permissionA, permissionB)).toStrictEqual(1);
162
+ expect(UpdatedPermissionsCollection.sortPermissionsByGranteeTypeAndName(permissionB, permissionA)).toStrictEqual(-1);
163
+ });
164
+
165
+ it("should put group after users", () => {
166
+ expect.assertions(2);
167
+
168
+ const permissionA = new UpdatedPermissionEntity(defaultUpdatePermissionDto({user: null, group: defaultGroupDto()}));
169
+ const permissionB = new UpdatedPermissionEntity(defaultUpdatePermissionDto({user: defaultUserDto()}));
170
+
171
+ expect(UpdatedPermissionsCollection.sortPermissionsByGranteeTypeAndName(permissionA, permissionB)).toStrictEqual(1);
172
+ expect(UpdatedPermissionsCollection.sortPermissionsByGranteeTypeAndName(permissionB, permissionA)).toStrictEqual(-1);
173
+ });
174
+
175
+ it("should put undefined group after users", () => {
176
+ expect.assertions(2);
177
+
178
+ const permissionA = new UpdatedPermissionEntity(defaultUpdatePermissionDto({user: null, group: null}));
179
+ const permissionB = new UpdatedPermissionEntity(defaultUpdatePermissionDto({user: defaultUserDto()}));
180
+
181
+ expect(UpdatedPermissionsCollection.sortPermissionsByGranteeTypeAndName(permissionA, permissionB)).toStrictEqual(1);
182
+ expect(UpdatedPermissionsCollection.sortPermissionsByGranteeTypeAndName(permissionB, permissionA)).toStrictEqual(-1);
183
+ });
184
+
185
+ it("should put undefined users after group", () => {
186
+ expect.assertions(2);
187
+
188
+ const permissionA = new UpdatedPermissionEntity(defaultUpdatePermissionDto({user: null, group: defaultGroupDto()}));
189
+ const permissionB = new UpdatedPermissionEntity(defaultUpdatePermissionDto({user: null}));
190
+
191
+ expect(UpdatedPermissionsCollection.sortPermissionsByGranteeTypeAndName(permissionA, permissionB)).toStrictEqual(-1);
192
+ expect(UpdatedPermissionsCollection.sortPermissionsByGranteeTypeAndName(permissionB, permissionA)).toStrictEqual(1);
193
+ });
194
+
195
+ it("should put undefined grantees at the same level", () => {
196
+ expect.assertions(1);
197
+
198
+ const permissionA = new UpdatedPermissionEntity(defaultUpdatePermissionDto({user: null, group: null}));
199
+ const permissionB = new UpdatedPermissionEntity(defaultUpdatePermissionDto({user: null, group: null}));
200
+
201
+ expect(UpdatedPermissionsCollection.sortPermissionsByGranteeTypeAndName(permissionA, permissionB)).toStrictEqual(0);
202
+ });
203
+ });
204
+ });
@@ -348,6 +348,84 @@ class PermissionsCollection extends EntityV2Collection {
348
348
  return permissions;
349
349
  }
350
350
 
351
+ /**
352
+ * Sort the current collection by aro and names.
353
+ * This method is mutatative
354
+ */
355
+ sort() {
356
+ this._items.sort(PermissionsCollection.sortPermissionsByAroAndName);
357
+ }
358
+
359
+ /**
360
+ * Delegate function to use as a callback to sort permissions.
361
+ * Permissions are sorted this way:
362
+ * - Defined users first
363
+ * - Defined groups second
364
+ * - Unknown users third
365
+ * - Unknown groups fourth
366
+ * Users are sorted by their `${user.first_name} ${user.last_name}`
367
+ * Groups are sorted by their `${group.name}`
368
+ *
369
+ * @param {PermissionEntity} permissionA
370
+ * @param {PermissionEntity} permissionB
371
+ * @returns {number} -1 if permissionA comes first, 1 if permissionB comes first, 0 it they are "equals"
372
+ * @static
373
+ * @private
374
+ */
375
+ static sortPermissionsByAroAndName(permissionA, permissionB) {
376
+ //compare AROs, if they are different, aro User is coming first in the list
377
+ const isADefinedUserPermission = permissionA.aro === PermissionEntity.ARO_USER && Boolean(permissionA.user);
378
+ const isBDefinedUserPermission = permissionB.aro === PermissionEntity.ARO_USER && Boolean(permissionB.user);
379
+
380
+ if (isADefinedUserPermission && isBDefinedUserPermission) {
381
+ //both users are defined, we need to order by their full name.
382
+ const userAName = permissionA.user.profile.name;
383
+ const userBName = permissionB.user.profile.name;
384
+
385
+ return userAName.localeCompare(userBName);
386
+ }
387
+
388
+ if (isADefinedUserPermission) {
389
+ // permissionA is a user, permissionB is either an undefined user, a group, an undefined group. So permissionA comes first.
390
+ return -1;
391
+ } else if (isBDefinedUserPermission) {
392
+ // permissionB is a user, permissionA is either an undefined user, a group, an undefined group. So permissionB comes first.
393
+ return 1;
394
+ }
395
+
396
+ //here both permissionA and permissionB are for group permission. But their group could be undefined though
397
+ const isADefinedGroupPermission = permissionA.aro === PermissionEntity.ARO_GROUP && Boolean(permissionA.group);
398
+ const isBDefinedGroupPermission = permissionB.aro === PermissionEntity.ARO_GROUP && Boolean(permissionB.group);
399
+
400
+ if (isADefinedGroupPermission && isBDefinedGroupPermission) {
401
+ // both permission are for defined group, we need to order them by their group name
402
+ const groupAName = permissionA.group.name;
403
+ const groupBName = permissionB.group.name;
404
+
405
+ return groupAName.localeCompare(groupBName);
406
+ }
407
+
408
+ if (isADefinedGroupPermission) {
409
+ // permissionA is for a defined group and permissionB is for an undefined group. permissionA comes first
410
+ return -1;
411
+ } else if (isBDefinedGroupPermission) {
412
+ // permissionB is for a defined group and permissionA is for an undefined group. permissionB comes first
413
+ return 1;
414
+ }
415
+
416
+ // both permissions are for undefined group or user
417
+ const isAUserPermission = permissionA.aro === PermissionEntity.ARO_USER;
418
+ const isBUserPermission = permissionB.aro === PermissionEntity.ARO_USER;
419
+ if ((isAUserPermission && isBUserPermission) || (!isAUserPermission && !isBUserPermission)) {
420
+ //they are both permissions for undefined user or both for undefined group. they are consider "equals"
421
+ return 0;
422
+ }
423
+
424
+ return isAUserPermission
425
+ ? -1 // permissionA is for an undefined user and permissionB for an undefined group. permissionA comes first
426
+ : 1; // permissionB is for an undefined user and permissionA for an undefined group. permissionB comes first
427
+ }
428
+
351
429
  /*
352
430
  * ==================================================
353
431
  * Static getters
@@ -12,10 +12,8 @@
12
12
  * @since 2.13.0
13
13
  */
14
14
  import PermissionEntity from "./permissionEntity";
15
- import permissionEntity from "./permissionEntity";
16
15
  import EntitySchema from "passbolt-styleguide/src/shared/models/entity/abstract/entitySchema";
17
16
  import PermissionsCollection from "./permissionsCollection";
18
- import permissionsCollection from "./permissionsCollection";
19
17
  import {
20
18
  defaultPermissionDto,
21
19
  minimumPermissionDto,
@@ -30,6 +28,8 @@ import {
30
28
  import {
31
29
  defaultPermissionsDtos
32
30
  } from "passbolt-styleguide/src/shared/models/entity/permission/permissionCollection.test.data";
31
+ import {defaultUserDto} from "passbolt-styleguide/src/shared/models/entity/user/userEntity.test.data";
32
+ import {defaultGroupDto} from "passbolt-styleguide/src/shared/models/entity/group/groupEntity.test.data";
33
33
 
34
34
  describe("PermissionsCollection", () => {
35
35
  it("schema must validate", () => {
@@ -77,9 +77,9 @@ describe("PermissionsCollection", () => {
77
77
  it("works if valid complete entities are provided", () => {
78
78
  expect.assertions(19);
79
79
  const acoForeignKey = crypto.randomUUID();
80
- const entity1 = new permissionEntity(defaultPermissionDto({aco_foreign_key: acoForeignKey}));
81
- const entity2 = new permissionEntity(defaultPermissionDto({aco_foreign_key: acoForeignKey}));
82
- const entity3 = new permissionEntity(defaultPermissionDto({aco_foreign_key: acoForeignKey}));
80
+ const entity1 = new PermissionEntity(defaultPermissionDto({aco_foreign_key: acoForeignKey}));
81
+ const entity2 = new PermissionEntity(defaultPermissionDto({aco_foreign_key: acoForeignKey}));
82
+ const entity3 = new PermissionEntity(defaultPermissionDto({aco_foreign_key: acoForeignKey}));
83
83
  const entities = [entity1, entity2, entity3];
84
84
  const collection = new PermissionsCollection(entities);
85
85
  expect(collection.items).toHaveLength(3);
@@ -165,7 +165,7 @@ describe("PermissionsCollection", () => {
165
165
  const dto3 = ownerPermissionDto({aco_foreign_key: acoForeignKey});
166
166
 
167
167
  expect.assertions(3);
168
- const collection = new permissionsCollection([dto1, dto2, dto3], {ignoreInvalidEntity: true});
168
+ const collection = new PermissionsCollection([dto1, dto2, dto3], {ignoreInvalidEntity: true});
169
169
  expect(collection.items).toHaveLength(2);
170
170
  expect(collection.items[0].id).toEqual(dto1.id);
171
171
  expect(collection.items[1].id).toEqual(dto3.id);
@@ -323,7 +323,7 @@ describe("PermissionsCollection", () => {
323
323
  const collection = new PermissionsCollection([dto1, dto2]);
324
324
 
325
325
  // same same
326
- collection.addOrReplace(new permissionEntity(dto3));
326
+ collection.addOrReplace(new PermissionEntity(dto3));
327
327
  expect(collection.permissions.length).toBe(2);
328
328
  expect(collection.permissions[1].type).toBe(1);
329
329
 
@@ -525,4 +525,136 @@ describe("PermissionsCollection", () => {
525
525
  expect(resultSet.toDto()).toEqual([]);
526
526
  });
527
527
  });
528
+
529
+ describe("::sortPermissionsByAroAndName", () => {
530
+ it("should order users by their name", () => {
531
+ expect.assertions(3);
532
+
533
+ const userA = defaultUserDto();
534
+ userA.profile.first_name = "user A";
535
+ const userB = defaultUserDto();
536
+ userB.profile.first_name = "user B";
537
+
538
+ const permissionA = new PermissionEntity(defaultPermissionDto({aro: "User", user: userA}));
539
+ const permissionB = new PermissionEntity(defaultPermissionDto({aro: "User", user: userB}));
540
+
541
+ expect(PermissionsCollection.sortPermissionsByAroAndName(permissionA, permissionB)).toStrictEqual(-1);
542
+ expect(PermissionsCollection.sortPermissionsByAroAndName(permissionB, permissionA)).toStrictEqual(1);
543
+ expect(PermissionsCollection.sortPermissionsByAroAndName(permissionA, permissionA)).toStrictEqual(0);
544
+ });
545
+
546
+ it("should consider equal permisions with users having the same name", () => {
547
+ expect.assertions(1);
548
+
549
+ const userA = defaultUserDto();
550
+ userA.profile.first_name = "user";
551
+ const userB = defaultUserDto();
552
+ userB.profile.first_name = "user";
553
+
554
+ const permissionA = new PermissionEntity(defaultPermissionDto({aro: "User", user: userA}));
555
+ const permissionB = new PermissionEntity(defaultPermissionDto({aro: "User", user: userB}));
556
+
557
+ expect(PermissionsCollection.sortPermissionsByAroAndName(permissionA, permissionB)).toStrictEqual(0);
558
+ });
559
+
560
+ it("should put user without definition after users with definition", () => {
561
+ expect.assertions(2);
562
+
563
+ const userB = defaultUserDto();
564
+ userB.profile.first_name = "user B";
565
+
566
+ const permissionA = new PermissionEntity(defaultPermissionDto({aro: "User", user: null}));
567
+ const permissionB = new PermissionEntity(defaultPermissionDto({aro: "User", user: userB}));
568
+
569
+ expect(PermissionsCollection.sortPermissionsByAroAndName(permissionA, permissionB)).toStrictEqual(1);
570
+ expect(PermissionsCollection.sortPermissionsByAroAndName(permissionB, permissionA)).toStrictEqual(-1);
571
+ });
572
+
573
+ it("both undfined users should be considered equal", () => {
574
+ expect.assertions(1);
575
+
576
+ const permissionA = new PermissionEntity(defaultPermissionDto({aro: "User", user: null}));
577
+ const permissionB = new PermissionEntity(defaultPermissionDto({aro: "User", user: null}));
578
+
579
+ expect(PermissionsCollection.sortPermissionsByAroAndName(permissionA, permissionB)).toStrictEqual(0);
580
+ });
581
+
582
+ it("should order groups by their name", () => {
583
+ expect.assertions(3);
584
+
585
+ const groupA = defaultGroupDto({name: "Group A"});
586
+ const groupB = defaultGroupDto({name: "Group B"});
587
+
588
+ const permissionA = new PermissionEntity(defaultPermissionDto({aro: "Group", group: groupA}));
589
+ const permissionB = new PermissionEntity(defaultPermissionDto({aro: "Group", group: groupB}));
590
+
591
+ expect(PermissionsCollection.sortPermissionsByAroAndName(permissionA, permissionB)).toStrictEqual(-1);
592
+ expect(PermissionsCollection.sortPermissionsByAroAndName(permissionB, permissionA)).toStrictEqual(1);
593
+ expect(PermissionsCollection.sortPermissionsByAroAndName(permissionA, permissionA)).toStrictEqual(0);
594
+ });
595
+
596
+ it("should consider equal permisions with groups having the same name", () => {
597
+ expect.assertions(1);
598
+
599
+ const groupA = defaultGroupDto({name: "Group"});
600
+ const groupB = defaultGroupDto({name: "Group"});
601
+
602
+ const permissionA = new PermissionEntity(defaultPermissionDto({aro: "Group", group: groupA}));
603
+ const permissionB = new PermissionEntity(defaultPermissionDto({aro: "Group", group: groupB}));
604
+
605
+ expect(PermissionsCollection.sortPermissionsByAroAndName(permissionA, permissionB)).toStrictEqual(0);
606
+ });
607
+
608
+ it("should put group without definition after groups with definition", () => {
609
+ expect.assertions(2);
610
+
611
+ const groupB = defaultGroupDto({name: "Group B"});
612
+
613
+ const permissionA = new PermissionEntity(defaultPermissionDto({aro: "Group", group: null}));
614
+ const permissionB = new PermissionEntity(defaultPermissionDto({aro: "Group", group: groupB}));
615
+
616
+ expect(PermissionsCollection.sortPermissionsByAroAndName(permissionA, permissionB)).toStrictEqual(1);
617
+ expect(PermissionsCollection.sortPermissionsByAroAndName(permissionB, permissionA)).toStrictEqual(-1);
618
+ });
619
+
620
+ it("should put group after users", () => {
621
+ expect.assertions(2);
622
+
623
+ const permissionA = new PermissionEntity(defaultPermissionDto({aro: "Group", group: defaultGroupDto()}));
624
+ const permissionB = new PermissionEntity(defaultPermissionDto({aro: "User", user: defaultUserDto()}));
625
+
626
+ expect(PermissionsCollection.sortPermissionsByAroAndName(permissionA, permissionB)).toStrictEqual(1);
627
+ expect(PermissionsCollection.sortPermissionsByAroAndName(permissionB, permissionA)).toStrictEqual(-1);
628
+ });
629
+
630
+ it("should put undefined group after users", () => {
631
+ expect.assertions(2);
632
+
633
+ const permissionA = new PermissionEntity(defaultPermissionDto({aro: "Group", group: null}));
634
+ const permissionB = new PermissionEntity(defaultPermissionDto({aro: "User", user: defaultUserDto()}));
635
+
636
+ expect(PermissionsCollection.sortPermissionsByAroAndName(permissionA, permissionB)).toStrictEqual(1);
637
+ expect(PermissionsCollection.sortPermissionsByAroAndName(permissionB, permissionA)).toStrictEqual(-1);
638
+ });
639
+
640
+ it("should put undefined users after group", () => {
641
+ expect.assertions(2);
642
+
643
+ const permissionA = new PermissionEntity(defaultPermissionDto({aro: "Group", group: defaultGroupDto()}));
644
+ const permissionB = new PermissionEntity(defaultPermissionDto({aro: "User", user: null}));
645
+
646
+ expect(PermissionsCollection.sortPermissionsByAroAndName(permissionA, permissionB)).toStrictEqual(-1);
647
+ expect(PermissionsCollection.sortPermissionsByAroAndName(permissionB, permissionA)).toStrictEqual(1);
648
+ });
649
+
650
+ it("should put undefined group after undefined user", () => {
651
+ expect.assertions(2);
652
+
653
+ const permissionA = new PermissionEntity(defaultPermissionDto({aro: "Group", group: null}));
654
+ const permissionB = new PermissionEntity(defaultPermissionDto({aro: "User", user: null}));
655
+
656
+ expect(PermissionsCollection.sortPermissionsByAroAndName(permissionA, permissionB)).toStrictEqual(1);
657
+ expect(PermissionsCollection.sortPermissionsByAroAndName(permissionB, permissionA)).toStrictEqual(-1);
658
+ });
659
+ });
528
660
  });
@@ -76,6 +76,15 @@ class PlaintextEntity extends EntityV2 {
76
76
  get totp() {
77
77
  return this._props.totp || null;
78
78
  }
79
+
80
+ /**
81
+ * Return custom fields prop if any
82
+ *
83
+ * @returns {object|null} custom fields
84
+ */
85
+ get customFields() {
86
+ return this._props.custom_fields || null;
87
+ }
79
88
  }
80
89
 
81
90
  export default PlaintextEntity;
@@ -19,8 +19,11 @@ import EntityV2 from "passbolt-styleguide/src/shared/models/entity/abstract/enti
19
19
  import EntitySchema from "passbolt-styleguide/src/shared/models/entity/abstract/entitySchema";
20
20
  import {assertType} from "../../../../utils/assertions";
21
21
  import IconEntity from "passbolt-styleguide/src/shared/models/entity/resource/metadata/IconEntity";
22
+ import CustomFieldsCollection from "passbolt-styleguide/src/shared/models/entity/customField/customFieldsCollection";
22
23
 
23
24
  const DEFAULT_RESOURCE_NAME = '(no name)';
25
+ const RESOURCE_URI_MAX_LENGTH = 1024;
26
+ const RESOURCE_URIS_MAX_ITEMS = 32;
24
27
 
25
28
  class ExternalResourceEntity extends EntityV2 {
26
29
  /**
@@ -54,6 +57,18 @@ class ExternalResourceEntity extends EntityV2 {
54
57
  }
55
58
  }
56
59
 
60
+
61
+ /**
62
+ * @inheritDoc
63
+ * @returns {{custom_fields: CustomFieldsCollection}}
64
+ */
65
+ static get associations() {
66
+ return {
67
+ custom_fields: CustomFieldsCollection,
68
+ };
69
+ }
70
+
71
+
57
72
  /**
58
73
  * @inheritdoc
59
74
  * Sanitize:
@@ -91,9 +106,13 @@ class ExternalResourceEntity extends EntityV2 {
91
106
  "id": resourceEntitySchema.properties.id,
92
107
  "name": metadataEntitySchema.properties.name,
93
108
  "username": metadataEntitySchema.properties.username,
94
- "uri": {
95
- "type": "string",
96
- "maxLength": ResourceMetadataEntity.URI_MAX_LENGTH,
109
+ "uris": {
110
+ "type": "array",
111
+ "items": {
112
+ "type": "string",
113
+ "maxLength": RESOURCE_URI_MAX_LENGTH
114
+ },
115
+ "maxItems": RESOURCE_URIS_MAX_ITEMS
97
116
  },
98
117
  "description": metadataEntitySchema.properties.description,
99
118
  "secrets": resourceEntitySchema.properties.secrets,
@@ -111,6 +130,10 @@ class ExternalResourceEntity extends EntityV2 {
111
130
  },
112
131
  "expired": resourceEntitySchema.properties.expired,
113
132
  "icon": IconEntity.getSchema(),
133
+ "custom_fields": {
134
+ ...CustomFieldsCollection.getSchema(),
135
+ "nullable": true,
136
+ },
114
137
  }
115
138
  };
116
139
  }
@@ -140,6 +163,10 @@ class ExternalResourceEntity extends EntityV2 {
140
163
  result.icon = this._icon.toDto();
141
164
  }
142
165
 
166
+ if (this._customFields) {
167
+ result.custom_fields = this._customFields.toDto();
168
+ }
169
+
143
170
  return result;
144
171
  }
145
172
 
@@ -162,13 +189,14 @@ class ExternalResourceEntity extends EntityV2 {
162
189
  id: resourceEntityDto.id,
163
190
  name: resourceEntityDto.metadata.name,
164
191
  username: resourceEntityDto.metadata.username,
165
- uri: resourceEntityDto.metadata.uris?.[0] || "",
192
+ uris: resourceEntityDto.metadata.uris || [],
166
193
  description: resourceEntityDto.metadata.description || null,
167
194
  secrets: resourceEntityDto.secrets || [],
168
195
  folder_parent_id: externalFolderParent?.id || null,
169
196
  resource_type_id: resourceEntityDto.resource_type_id,
170
197
  folder_parent_path: externalFolderParent?.path || "",
171
198
  expired: resourceEntityDto.expired || null,
199
+ custom_fields: resourceEntityDto.metadata.custom_fields || [],
172
200
  };
173
201
 
174
202
  if (resourceEntityDto.metadata.icon) {
@@ -178,6 +206,15 @@ class ExternalResourceEntity extends EntityV2 {
178
206
  return data;
179
207
  }
180
208
 
209
+ /**
210
+ * ResourceMetadataEntity.URI_MAX_LENGTH
211
+ * @returns {number}
212
+ */
213
+ static get URI_MAX_LENGTH() {
214
+ return RESOURCE_URI_MAX_LENGTH;
215
+ }
216
+
217
+
181
218
  /**
182
219
  * Returns a Resource DTO in v5 format.
183
220
  * @returns {Object}
@@ -188,9 +225,10 @@ class ExternalResourceEntity extends EntityV2 {
188
225
  object_type: ResourceMetadataEntity.METADATA_OBJECT_TYPE,
189
226
  name: this.name,
190
227
  username: this.username,
191
- uris: [this.uri || ""],
228
+ uris: this.uris,
192
229
  description: this.description,
193
230
  resource_type_id: this.resourceTypeId,
231
+ custom_fields: this.customFields?.toMetadataDto()
194
232
  },
195
233
  secrets: this._secrets.toDto(),
196
234
  folder_parent_id: this.folderParentId,
@@ -237,11 +275,11 @@ class ExternalResourceEntity extends EntityV2 {
237
275
  }
238
276
 
239
277
  /**
240
- * Get resource uri
278
+ * Get resource uris
241
279
  * @returns {string|null}
242
280
  */
243
- get uri() {
244
- return this._props.uri || null;
281
+ get uris() {
282
+ return this._props.uris || [];
245
283
  }
246
284
 
247
285
  /**
@@ -430,11 +468,30 @@ class ExternalResourceEntity extends EntityV2 {
430
468
  return this._icon || null;
431
469
  }
432
470
 
471
+ /**
472
+ * Get the custom fields collection
473
+ * @returns {CustomFieldsCollection|null}
474
+ */
475
+ get customFields() {
476
+ return this._customFields || null;
477
+ }
478
+
479
+ /**
480
+ * Set the custom fields
481
+ * @param {CustomFieldsCollection} customFields the custom fields collection to us
482
+ * @returns {void}
483
+ */
484
+ set customFields(customFields) {
485
+ assertType(customFields, CustomFieldsCollection);
486
+ this._customFields = customFields;
487
+ }
488
+
433
489
  /*
434
490
  * ==================================================
435
491
  * Static properties getters
436
492
  * ==================================================
437
493
  */
494
+
438
495
  /**
439
496
  * ExternalResourceEntity.DEFAULT_RESOURCE_NAME
440
497
  * @returns {string}