passbolt-browser-extension 4.9.1 → 4.9.2-rc.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.
- package/CHANGELOG.md +47 -1
- package/RELEASE_NOTES.md +47 -5
- package/doc/resource-types-retrieval-requirements.md +17 -0
- package/package.json +4 -8
- package/src/all/background_page/controller/InformMenuController/InformMenuController.js +2 -2
- package/src/all/background_page/controller/autofill/AutofillController.js +1 -1
- package/src/all/background_page/controller/autofill/AutofillController.test.js +42 -0
- package/src/all/background_page/controller/import/importResourcesFileController.js +1 -1
- package/src/all/background_page/controller/move/moveResourcesController.js +5 -4
- package/src/all/background_page/controller/resource/resourceCreateController.js +22 -90
- package/src/all/background_page/controller/resource/resourceCreateController.test.js +120 -0
- package/src/all/background_page/controller/share/shareFoldersController.js +1 -1
- package/src/all/background_page/event/resourceEvents.js +2 -8
- package/src/all/background_page/model/entity/export/exportResourcesFileEntity.js +8 -0
- package/src/all/background_page/model/entity/export/exportResourcesFileEntity.test.js +4 -0
- package/src/all/background_page/model/entity/folder/folderEntity.js +1 -0
- package/src/all/background_page/model/entity/folder/folderEntity.test.js +19 -0
- package/src/all/background_page/model/entity/permission/permissionsCollection.js +2 -2
- package/src/all/background_page/model/entity/permission/permissionsCollection.test.js +24 -2
- package/src/all/background_page/model/entity/resource/external/externalResourceEntity.js +52 -4
- package/src/all/background_page/model/entity/resource/external/externalResourceEntity.test.data.js +56 -0
- package/src/all/background_page/model/entity/resource/external/externalResourceEntity.test.js +74 -0
- package/src/all/background_page/model/entity/resource/external/externalResourcesCollection.js +16 -3
- package/src/all/background_page/model/entity/resource/external/externalResourcesCollection.test.data.js +21 -0
- package/src/all/background_page/model/entity/resource/external/externalResourcesCollection.test.js +24 -0
- package/src/all/background_page/model/entity/resource/metadata/resourceMetadataEntity.js +146 -0
- package/src/all/background_page/model/entity/resource/metadata/resourceMetadataEntity.test.data.js +47 -0
- package/src/all/background_page/model/entity/resource/metadata/resourceMetadataEntity.test.js +90 -0
- package/src/all/background_page/model/entity/resource/resourceEntity.js +67 -61
- package/src/all/background_page/model/entity/resource/resourceEntity.test.js +63 -132
- package/src/all/background_page/model/entity/resource/resourcesCollection.js +17 -3
- package/src/all/background_page/model/entity/resource/resourcesCollection.test.data.js +5 -6
- package/src/all/background_page/model/entity/resource/resourcesCollection.test.js +33 -37
- package/src/all/background_page/model/resource/resourceModel.js +3 -17
- package/src/all/background_page/model/share/shareModel.js +3 -3
- package/src/all/background_page/service/accountRecovery/decryptPrivateKeyPasswordDataService.js +1 -1
- package/src/all/background_page/service/crypto/decryptPrivateKeyService.js +14 -0
- package/src/all/background_page/service/crypto/decryptPrivateKeyService.test.js +5 -0
- package/src/all/background_page/service/local_storage/resourceLocalStorage.test.js +16 -16
- package/src/all/background_page/service/resource/create/resourceCreateService.js +127 -0
- package/src/all/background_page/service/resource/create/resourceCreateService.test.data.js +55 -0
- package/src/all/background_page/service/resource/create/resourceCreateService.test.js +236 -0
- package/src/all/background_page/service/toolbar/toolbarService.js +2 -6
- package/src/all/locales/fr-FR/common.json +5 -5
- package/src/all/locales/pl-PL/common.json +2 -2
- package/src/chrome/manifest.json +1 -1
- package/src/chrome-mv3/manifest.json +1 -1
- package/src/firefox/manifest.json +1 -1
- package/src/safari/manifest.json +1 -1
- package/test/matchers/toThrowEntityValidationError.js +7 -4
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,51 @@ All notable changes to this project will be documented in this file.
|
|
|
3
3
|
This project adheres to [Semantic Versioning](http://semver.org/).
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
|
+
## [4.9.2] - 2024-08-26
|
|
7
|
+
### Fixed
|
|
8
|
+
- PB-33861: Resources with personal field set to null should be considered as personal resources
|
|
9
|
+
- PB-34314: Fix shadow-dom autofill fields
|
|
10
|
+
- PB-34236: Fix Retrieving folder activities displaying no data
|
|
11
|
+
|
|
12
|
+
### Maintenance
|
|
13
|
+
- PB-34313: Add resources type retrieval requirements documentation
|
|
14
|
+
- PB-34259: E2EE WP1 - Transform dtos from v4 to v5
|
|
15
|
+
- PB-34260: E2EE WP1 - Display resource sidebar information section in v5
|
|
16
|
+
- PB-34261: E2EE WP1 - Display resource sidebar activity section in v5
|
|
17
|
+
- PB-34262: E2EE WP1 - Display resource sidebar description section in v5
|
|
18
|
+
- PB-34263: E2EE WP1 - Display copy username to clipboard from more menu using v5
|
|
19
|
+
- PB-34264: E2EE WP1 - Display resource grid using v5
|
|
20
|
+
- PB-34265: E2EE WP1 - Display resource grid contextual menu using v5
|
|
21
|
+
- PB-34266: E2EE WP1 - Display quickaccess resource view page in v5
|
|
22
|
+
- PB-34267: E2EE WP1 - Display quickaccess home page in v5
|
|
23
|
+
- PB-34268: E2EE WP1 - Display inform menu in v5
|
|
24
|
+
- PB-34269: E2EE WP1 - Autofill resources from Quickaccess in v5 format
|
|
25
|
+
- PB-34270: E2EE WP1 - Make resource entity compatible with v4 and v5
|
|
26
|
+
- PB-34271: E2EE WP1 - Display inform and toolbar suggested resources badge CTA in v5
|
|
27
|
+
- PB-34272: E2EE WP1 - Search resource in webapp using v5
|
|
28
|
+
- PB-34287: E2EE WP1 - Create password resource from webapp in v5 format
|
|
29
|
+
- PB-34288: E2EE WP1 - Create standalone TOTP resource in v5 format
|
|
30
|
+
- PB-34289: E2EE WP1 - Edit password resource in v5 format
|
|
31
|
+
- PB-34290: E2EE WP1 - Edit standalone TOTP resource in v5 format
|
|
32
|
+
- PB-34291: E2EE WP1 - Edit resource description from sidebar in v5 format
|
|
33
|
+
- PB-34292: E2EE WP1 - Delete resource(s) in v5 format
|
|
34
|
+
- PB-34293: E2EE WP1 - Share resource(s) in v5 format
|
|
35
|
+
- PB-34294: E2EE WP1 - Import resource(s) in v5 format
|
|
36
|
+
- PB-34295: E2EE WP1 - Export resource(s) in v5 format
|
|
37
|
+
- PB-34296: E2EE WP1 - Move resource(s) in v5 format
|
|
38
|
+
- PB-34297: E2EE WP1 - Create password resource from quickaccess in v5 format
|
|
39
|
+
- PB-34298: E2EE WP1 - Auto-save password resource from quickaccess in v5 format
|
|
40
|
+
- PB-34299: E2EE WP1 - Make resource entity compatible only with v5
|
|
41
|
+
- PB-34311: E2EE WP1 - Make resource V4 and V5 compatible in both ways
|
|
42
|
+
- PB-34315: E2EE WP1 - Transform DTO to V4 for API and adapt resource validation to v5
|
|
43
|
+
- PB-34391: E2EE WP1 - Enforce resource type id should be required and not null
|
|
44
|
+
- PB-34392: E2EE WP1 - Validate Metadata.uris as array of string, and maxLength
|
|
45
|
+
|
|
46
|
+
### Security
|
|
47
|
+
- PB-34237: Upgrade vulnerable library i18next-parser
|
|
48
|
+
- PB-34305: Upgrade lockfile-lint library on passbolt_api package-lock.json
|
|
49
|
+
- PB-34422: Remove grunt-browserify dev dependency from browser extension
|
|
50
|
+
|
|
6
51
|
## [4.9.1] - 2024-07-23
|
|
7
52
|
### Fixed
|
|
8
53
|
- PB-34134 As a signed-in user I should search resources even if the data integrity is corrupted
|
|
@@ -1719,7 +1764,8 @@ self registration settings option in the left-side bar
|
|
|
1719
1764
|
- AP: User with plugin installed
|
|
1720
1765
|
- LU: Logged in user
|
|
1721
1766
|
|
|
1722
|
-
[Unreleased]: https://github.com/passbolt/passbolt_browser_extension/compare/v4.9.
|
|
1767
|
+
[Unreleased]: https://github.com/passbolt/passbolt_browser_extension/compare/v4.9.2...HEAD
|
|
1768
|
+
[4.9.2]: https://github.com/passbolt/passbolt_browser_extension/compare/v4.9.1...4.9.2
|
|
1723
1769
|
[4.9.1]: https://github.com/passbolt/passbolt_browser_extension/compare/v4.9.0...4.9.1
|
|
1724
1770
|
[4.9.0]: https://github.com/passbolt/passbolt_browser_extension/compare/v4.8.2...4.9.0
|
|
1725
1771
|
[4.8.2]: https://github.com/passbolt/passbolt_browser_extension/compare/v4.8.1...4.8.2
|
package/RELEASE_NOTES.md
CHANGED
|
@@ -1,9 +1,51 @@
|
|
|
1
|
-
Song: https://www.youtube.com/watch?v=
|
|
1
|
+
Song: https://www.youtube.com/watch?v=VmtU-bLyReU
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This release candidate addresses several bugs reported by the community. Additionally, it includes numerous maintenance updates as part of our ongoing efforts to ensure a smooth transition and support for the upcoming v5.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Thank you to the community for reporting these issues.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
|
|
8
|
+
## [4.9.2] - 2024-08-26
|
|
8
9
|
### Fixed
|
|
9
|
-
- PB-
|
|
10
|
+
- PB-33861: Resources with personal field set to null should be considered as personal resources
|
|
11
|
+
- PB-34314: Fix shadow-dom autofill fields
|
|
12
|
+
- PB-34236: Fix Retrieving folder activities displaying no data
|
|
13
|
+
|
|
14
|
+
### Maintenance
|
|
15
|
+
- PB-34313: Add resources type retrieval requirements documentation
|
|
16
|
+
- PB-34259: E2EE WP1 - Transform dtos from v4 to v5
|
|
17
|
+
- PB-34260: E2EE WP1 - Display resource sidebar information section in v5
|
|
18
|
+
- PB-34261: E2EE WP1 - Display resource sidebar activity section in v5
|
|
19
|
+
- PB-34262: E2EE WP1 - Display resource sidebar description section in v5
|
|
20
|
+
- PB-34263: E2EE WP1 - Display copy username to clipboard from more menu using v5
|
|
21
|
+
- PB-34264: E2EE WP1 - Display resource grid using v5
|
|
22
|
+
- PB-34265: E2EE WP1 - Display resource grid contextual menu using v5
|
|
23
|
+
- PB-34266: E2EE WP1 - Display quickaccess resource view page in v5
|
|
24
|
+
- PB-34267: E2EE WP1 - Display quickaccess home page in v5
|
|
25
|
+
- PB-34268: E2EE WP1 - Display inform menu in v5
|
|
26
|
+
- PB-34269: E2EE WP1 - Autofill resources from Quickaccess in v5 format
|
|
27
|
+
- PB-34270: E2EE WP1 - Make resource entity compatible with v4 and v5
|
|
28
|
+
- PB-34271: E2EE WP1 - Display inform and toolbar suggested resources badge CTA in v5
|
|
29
|
+
- PB-34272: E2EE WP1 - Search resource in webapp using v5
|
|
30
|
+
- PB-34287: E2EE WP1 - Create password resource from webapp in v5 format
|
|
31
|
+
- PB-34288: E2EE WP1 - Create standalone TOTP resource in v5 format
|
|
32
|
+
- PB-34289: E2EE WP1 - Edit password resource in v5 format
|
|
33
|
+
- PB-34290: E2EE WP1 - Edit standalone TOTP resource in v5 format
|
|
34
|
+
- PB-34291: E2EE WP1 - Edit resource description from sidebar in v5 format
|
|
35
|
+
- PB-34292: E2EE WP1 - Delete resource(s) in v5 format
|
|
36
|
+
- PB-34293: E2EE WP1 - Share resource(s) in v5 format
|
|
37
|
+
- PB-34294: E2EE WP1 - Import resource(s) in v5 format
|
|
38
|
+
- PB-34295: E2EE WP1 - Export resource(s) in v5 format
|
|
39
|
+
- PB-34296: E2EE WP1 - Move resource(s) in v5 format
|
|
40
|
+
- PB-34297: E2EE WP1 - Create password resource from quickaccess in v5 format
|
|
41
|
+
- PB-34298: E2EE WP1 - Auto-save password resource from quickaccess in v5 format
|
|
42
|
+
- PB-34299: E2EE WP1 - Make resource entity compatible only with v5
|
|
43
|
+
- PB-34311: E2EE WP1 - Make resource V4 and V5 compatible in both ways
|
|
44
|
+
- PB-34315: E2EE WP1 - Transform DTO to V4 for API and adapt resource validation to v5
|
|
45
|
+
- PB-34391: E2EE WP1 - Enforce resource type id should be required and not null
|
|
46
|
+
- PB-34392: E2EE WP1 - Validate Metadata.uris as array of string, and maxLength
|
|
47
|
+
|
|
48
|
+
### Security
|
|
49
|
+
- PB-34237: Upgrade vulnerable library i18next-parser
|
|
50
|
+
- PB-34305: Upgrade lockfile-lint library on passbolt_api package-lock.json
|
|
51
|
+
- PB-34422: Remove grunt-browserify dev dependency from browser extension
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
```mermaid
|
|
2
|
+
graph
|
|
3
|
+
A[Request API]
|
|
4
|
+
B[Marshall resource metadata]
|
|
5
|
+
C[Create ResourceEntity]
|
|
6
|
+
D[Failure]
|
|
7
|
+
E[Decrypt metadata]
|
|
8
|
+
C1{ }
|
|
9
|
+
C2{ }
|
|
10
|
+
A --> |is v4 resource type?| C1
|
|
11
|
+
C1 --> |no, is v6 resource type?| C2
|
|
12
|
+
C1 --> |yes| B
|
|
13
|
+
B --> C
|
|
14
|
+
C2 --> |no| D
|
|
15
|
+
C2 --> |yes| E
|
|
16
|
+
E --> C
|
|
17
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "passbolt-browser-extension",
|
|
3
|
-
"version": "4.9.
|
|
3
|
+
"version": "4.9.2-rc.0",
|
|
4
4
|
"license": "AGPL-3.0",
|
|
5
5
|
"copyright": "Copyright 2022 Passbolt SA",
|
|
6
6
|
"description": "Passbolt web extension for the open source password manager for teams",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"locutus": "~2.0.9",
|
|
22
22
|
"openpgp": "^5.11.1",
|
|
23
23
|
"papaparse": "^5.2.0",
|
|
24
|
-
"passbolt-styleguide": "^4.9.
|
|
24
|
+
"passbolt-styleguide": "^4.9.5",
|
|
25
25
|
"react": "17.0.2",
|
|
26
26
|
"react-dom": "17.0.2",
|
|
27
27
|
"secrets-passbolt": "github:passbolt/secrets.js#v2.0.1",
|
|
@@ -44,11 +44,10 @@
|
|
|
44
44
|
"eslint-plugin-no-unsanitized": "^4.0.1",
|
|
45
45
|
"eslint-plugin-react": "^7.33.1",
|
|
46
46
|
"grunt": "^1.0.3",
|
|
47
|
-
"grunt-browserify": "^6.0.0",
|
|
48
47
|
"grunt-contrib-clean": "^2.0.0",
|
|
49
48
|
"grunt-contrib-copy": "^1.0.0",
|
|
50
49
|
"grunt-shell": "^4.0.0",
|
|
51
|
-
"i18next-parser": "^
|
|
50
|
+
"i18next-parser": "^9.0.1",
|
|
52
51
|
"jest": "^29.6.2",
|
|
53
52
|
"jest-each": "^29.6.2",
|
|
54
53
|
"jest-environment-jsdom": "^29.6.2",
|
|
@@ -56,7 +55,7 @@
|
|
|
56
55
|
"jest-fetch-mock": "^3.0.3",
|
|
57
56
|
"jest-junit": "^16.0.0",
|
|
58
57
|
"jest-webextension-mock": "^3.8.9",
|
|
59
|
-
"lockfile-lint": "^4.
|
|
58
|
+
"lockfile-lint": "^4.14.0",
|
|
60
59
|
"text-encoding-utf-8": "^1.0.2",
|
|
61
60
|
"uuid": "^8.3.2",
|
|
62
61
|
"web-ext": "^8.0.0",
|
|
@@ -70,9 +69,6 @@
|
|
|
70
69
|
"glob-parent": "5.1.2"
|
|
71
70
|
},
|
|
72
71
|
"firefox-profile": "4.3.2",
|
|
73
|
-
"crypto-browserify": {
|
|
74
|
-
"browserify-sign": "4.2.2"
|
|
75
|
-
},
|
|
76
72
|
"ws": "8.17.1"
|
|
77
73
|
},
|
|
78
74
|
"scripts": {
|
|
@@ -16,11 +16,11 @@ import ResourceModel from "../../model/resource/resourceModel";
|
|
|
16
16
|
import {QuickAccessService} from "../../service/ui/quickAccess.service";
|
|
17
17
|
import GetPassphraseService from "../../service/passphrase/getPassphraseService";
|
|
18
18
|
import BrowserTabService from "../../service/ui/browserTab.service";
|
|
19
|
-
import ResourceEntity from "../../model/entity/resource/resourceEntity";
|
|
20
19
|
import ExternalResourceEntity from "../../model/entity/resource/external/externalResourceEntity";
|
|
21
20
|
import ResourceInProgressCacheService from "../../service/cache/resourceInProgressCache.service";
|
|
22
21
|
import WorkerService from "../../service/worker/workerService";
|
|
23
22
|
import ResourceTypeModel from "../../model/resourceType/resourceTypeModel";
|
|
23
|
+
import ResourceMetadataEntity from "../../model/entity/resource/metadata/resourceMetadataEntity";
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
26
|
* Controller related to the in-form call-to-action
|
|
@@ -89,7 +89,7 @@ class InformMenuController {
|
|
|
89
89
|
// Retrieve resource name and uri from tab.
|
|
90
90
|
const tab = await BrowserTabService.getCurrent();
|
|
91
91
|
const name = tab.title;
|
|
92
|
-
const uri = tab.url.substr(0,
|
|
92
|
+
const uri = tab.url.substr(0, ResourceMetadataEntity.URI_MAX_LENGTH);
|
|
93
93
|
|
|
94
94
|
// Store the resource to save in cache.
|
|
95
95
|
const resourceDto = {name: name, username: username, uri: uri, secret_clear: secret_clear};
|
|
@@ -70,7 +70,7 @@ class AutofillController {
|
|
|
70
70
|
const secretSchema = await this.resourceTypeModel.getSecretSchemaById(resource.resourceTypeId);
|
|
71
71
|
const privateKey = await GetDecryptedUserPrivateKeyService.getKey(passphrase);
|
|
72
72
|
const plaintextSecret = await DecryptAndParseResourceSecretService.decryptAndParse(resource.secret, secretSchema, privateKey);
|
|
73
|
-
const
|
|
73
|
+
const username = resource.metadata?.username || "";
|
|
74
74
|
const password = plaintextSecret?.password;
|
|
75
75
|
this.fillCredential(webIntegrationWorker, {username, password});
|
|
76
76
|
} finally {
|
|
@@ -105,6 +105,7 @@ describe("AutofillController", () => {
|
|
|
105
105
|
expect(portWrapper.request).toHaveBeenCalledWith('passbolt.quickaccess.fill-form', resource.username, secret.password, tab.url);
|
|
106
106
|
expect(portWrapper.emit).not.toHaveBeenCalledWith('passbolt.in-form-menu.close');
|
|
107
107
|
});
|
|
108
|
+
|
|
108
109
|
|
|
109
110
|
it("Should not autofill from a worker that is not inform menu or quickaccess.", async() => {
|
|
110
111
|
expect.assertions(10);
|
|
@@ -144,6 +145,47 @@ describe("AutofillController", () => {
|
|
|
144
145
|
expect(portWrapper.request).not.toHaveBeenCalledWith('passbolt.quickaccess.fill-form', resource.username, secret.password, tab.url);
|
|
145
146
|
expect(portWrapper.emit).not.toHaveBeenCalledWith('passbolt.in-form-menu.close');
|
|
146
147
|
});
|
|
148
|
+
|
|
149
|
+
it("Should map username with empty string if not exist.", async() => {
|
|
150
|
+
expect.assertions(10);
|
|
151
|
+
|
|
152
|
+
// initialisation
|
|
153
|
+
const requestId = uuidv4();
|
|
154
|
+
const worker = readWorker();
|
|
155
|
+
const controller = new AutofillController(worker, requestId, defaultApiClientOptions(), account);
|
|
156
|
+
const resource = defaultResourceDto({
|
|
157
|
+
username: null
|
|
158
|
+
});
|
|
159
|
+
const secret = {password: "secret"};
|
|
160
|
+
const port = mockPort({name: worker.id, tabId: worker.tabId, frameId: worker.frameId});
|
|
161
|
+
const portWrapper = new Port(port);
|
|
162
|
+
const tab = {url: "https://url.com"};
|
|
163
|
+
// mocked function
|
|
164
|
+
jest.spyOn(WorkerService, "get").mockImplementationOnce(() => ({port: portWrapper, tab: tab}));
|
|
165
|
+
jest.spyOn(controller.getPassphraseService, "requestPassphraseFromQuickAccess");
|
|
166
|
+
jest.spyOn(controller.getPassphraseService, "getPassphrase").mockImplementationOnce(() => pgpKeys.ada.passphrase);
|
|
167
|
+
jest.spyOn(controller.resourceModel, "findForDecrypt").mockImplementationOnce(() => resource);
|
|
168
|
+
jest.spyOn(controller.resourceTypeModel, "getSecretSchemaById").mockImplementationOnce(jest.fn());
|
|
169
|
+
jest.spyOn(GetDecryptedUserPrivateKeyService, "getKey").mockImplementationOnce(() => pgpKeys.ada.private_decrypted);
|
|
170
|
+
jest.spyOn(DecryptAndParseResourceSecretService, "decryptAndParse").mockImplementationOnce(() => secret);
|
|
171
|
+
jest.spyOn(portWrapper, "emit");
|
|
172
|
+
jest.spyOn(portWrapper, "request");
|
|
173
|
+
|
|
174
|
+
// process
|
|
175
|
+
await controller.exec(resource.id, worker.tabId);
|
|
176
|
+
|
|
177
|
+
// expectations
|
|
178
|
+
expect(controller.getPassphraseService.requestPassphraseFromQuickAccess).not.toHaveBeenCalled();
|
|
179
|
+
expect(controller.getPassphraseService.getPassphrase).toHaveBeenCalledTimes(1);
|
|
180
|
+
expect(controller.getPassphraseService.getPassphrase).toHaveBeenCalledWith(worker);
|
|
181
|
+
expect(controller.resourceModel.findForDecrypt).toHaveBeenCalledTimes(1);
|
|
182
|
+
expect(controller.resourceModel.findForDecrypt).toHaveBeenCalledWith(resource.id);
|
|
183
|
+
expect(controller.resourceTypeModel.getSecretSchemaById).toHaveBeenCalledTimes(1);
|
|
184
|
+
expect(controller.resourceTypeModel.getSecretSchemaById).toHaveBeenCalledWith(resource.resourceTypeId);
|
|
185
|
+
expect(portWrapper.emit).not.toHaveBeenCalledWith('passbolt.web-integration.fill-credentials', {username: "", password: secret.password});
|
|
186
|
+
expect(portWrapper.request).not.toHaveBeenCalledWith('passbolt.quickaccess.fill-form', "", secret.password, tab.url);
|
|
187
|
+
expect(portWrapper.emit).not.toHaveBeenCalledWith('passbolt.in-form-menu.close');
|
|
188
|
+
});
|
|
147
189
|
});
|
|
148
190
|
});
|
|
149
191
|
|
|
@@ -248,7 +248,7 @@ class ImportResourcesFileController {
|
|
|
248
248
|
*/
|
|
249
249
|
async bulkImportResources(importEntity) {
|
|
250
250
|
let importedCount = 0;
|
|
251
|
-
const resourcesCollection = new ResourcesCollection(importEntity.importResources.
|
|
251
|
+
const resourcesCollection = new ResourcesCollection(importEntity.importResources.toResourceCollectionImportDto());
|
|
252
252
|
const successCallback = (resourceEntity, index) => this.handleImportResourceSuccess(importEntity, ++importedCount, resourceEntity, importEntity.importResources.items[index]);
|
|
253
253
|
const errorCallback = (error, index) => this.handleImportResourceError(importEntity, ++importedCount, error, importEntity.importResources.items[index]);
|
|
254
254
|
await this.resourceModel.bulkCreate(resourcesCollection, {successCallback: successCallback, errorCallback: errorCallback});
|
|
@@ -76,6 +76,7 @@ class MoveResourcesController {
|
|
|
76
76
|
await this.progressService.close();
|
|
77
77
|
this.cleanup();
|
|
78
78
|
} catch (error) {
|
|
79
|
+
console.error(error);
|
|
79
80
|
await this.progressService.close();
|
|
80
81
|
this.cleanup();
|
|
81
82
|
throw error;
|
|
@@ -144,7 +145,7 @@ class MoveResourcesController {
|
|
|
144
145
|
parent = this.resourcesParentFolders.getById(resource.folderParentId);
|
|
145
146
|
}
|
|
146
147
|
if (!ResourceEntity.canResourceMove(resource, parent, this.destinationFolder)) {
|
|
147
|
-
console.warn(`Resource ${resource.name} can not be moved, skipping.`);
|
|
148
|
+
console.warn(`Resource ${resource.metadata.name} can not be moved, skipping.`);
|
|
148
149
|
resourceIdsToRemove.push(resource.id);
|
|
149
150
|
}
|
|
150
151
|
}
|
|
@@ -166,7 +167,7 @@ class MoveResourcesController {
|
|
|
166
167
|
async calculateChanges() {
|
|
167
168
|
this.changes = new PermissionChangesCollection([]);
|
|
168
169
|
for (const resource of this.resources) {
|
|
169
|
-
await this.progressService.finishStep(i18n.t('Calculating changes for {{name}}', {name: resource.name}));
|
|
170
|
+
await this.progressService.finishStep(i18n.t('Calculating changes for {{name}}', {name: resource.metadata.name}));
|
|
170
171
|
/*
|
|
171
172
|
* A user who can update a resource can move it
|
|
172
173
|
* But to change the rights they need to be owner
|
|
@@ -201,10 +202,10 @@ class MoveResourcesController {
|
|
|
201
202
|
const resourceIdsToRemove = [];
|
|
202
203
|
for (const resource of this.resources) {
|
|
203
204
|
if (resource.folderParentId !== this.destinationFolderId) {
|
|
204
|
-
await this.progressService.finishStep(i18n.t('Moving {{name}}', {name: resource.name}));
|
|
205
|
+
await this.progressService.finishStep(i18n.t('Moving {{name}}', {name: resource.metadata.name}));
|
|
205
206
|
await this.resourceModel.move(resource, this.destinationFolderId);
|
|
206
207
|
} else {
|
|
207
|
-
Log.write({level: 'debug', message: `Resource ${resource.name} is already in the folder, skipping.`});
|
|
208
|
+
Log.write({level: 'debug', message: `Resource ${resource.metadata.name} is already in the folder, skipping.`});
|
|
208
209
|
resourceIdsToRemove.push(resource.id);
|
|
209
210
|
}
|
|
210
211
|
}
|
|
@@ -11,19 +11,11 @@
|
|
|
11
11
|
* @link https://www.passbolt.com Passbolt(tm)
|
|
12
12
|
* @since 2.9.0
|
|
13
13
|
*/
|
|
14
|
-
|
|
15
|
-
import Keyring from "../../model/keyring";
|
|
16
|
-
import EncryptMessageService from "../../service/crypto/encryptMessageService";
|
|
17
|
-
import User from "../../model/user";
|
|
18
|
-
import ResourceModel from "../../model/resource/resourceModel";
|
|
19
|
-
import GetPassphraseService from "../../service/passphrase/getPassphraseService";
|
|
20
|
-
import GetDecryptedUserPrivateKeyService from "../../service/account/getDecryptedUserPrivateKeyService";
|
|
21
|
-
import FolderModel from "../../model/folder/folderModel";
|
|
22
|
-
import ResourceEntity from "../../model/entity/resource/resourceEntity";
|
|
14
|
+
|
|
23
15
|
import i18n from "../../sdk/i18n";
|
|
24
|
-
import
|
|
16
|
+
import GetPassphraseService from "../../service/passphrase/getPassphraseService";
|
|
25
17
|
import ProgressService from "../../service/progress/progressService";
|
|
26
|
-
import
|
|
18
|
+
import ResourceCreateService from "../../service/resource/create/resourceCreateService";
|
|
27
19
|
|
|
28
20
|
class ResourceCreateController {
|
|
29
21
|
/**
|
|
@@ -37,102 +29,42 @@ class ResourceCreateController {
|
|
|
37
29
|
constructor(worker, requestId, apiClientOptions, account) {
|
|
38
30
|
this.worker = worker;
|
|
39
31
|
this.requestId = requestId;
|
|
40
|
-
this.resourceModel = new ResourceModel(apiClientOptions, account);
|
|
41
|
-
this.folderModel = new FolderModel(apiClientOptions);
|
|
42
|
-
this.shareModel = new ShareModel(apiClientOptions);
|
|
43
|
-
this.keyring = new Keyring();
|
|
44
32
|
this.progressService = new ProgressService(this.worker, i18n.t('Creating password'));
|
|
33
|
+
this.resourceCreateService = new ResourceCreateService(account, apiClientOptions, this.progressService);
|
|
45
34
|
this.getPassphraseService = new GetPassphraseService(account);
|
|
46
35
|
}
|
|
47
36
|
|
|
48
37
|
/**
|
|
49
|
-
*
|
|
50
|
-
*
|
|
38
|
+
* Controller executor.
|
|
51
39
|
* @param {object} resourceDto The resource data
|
|
52
40
|
* @param {string|object} plaintextDto The secret to encrypt
|
|
53
|
-
* @
|
|
41
|
+
* @returns {Promise<void>}
|
|
54
42
|
*/
|
|
55
|
-
async
|
|
56
|
-
let privateKey;
|
|
57
|
-
|
|
58
|
-
/*
|
|
59
|
-
* set default goals, we give arbitrarily "more" goals if a parent permission folder is set
|
|
60
|
-
* as we don't know how many 'share' operations are needed yet
|
|
61
|
-
*/
|
|
62
|
-
let resource = new ResourceEntity(resourceDto);
|
|
63
|
-
const goals = resource.folderParentId ? 10 : 2;
|
|
64
|
-
|
|
65
|
-
// Get the passphrase if needed and decrypt secret key
|
|
43
|
+
async _exec(resourceDto, plaintextDto) {
|
|
66
44
|
try {
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
} catch (error) {
|
|
70
|
-
console.error(error);
|
|
71
|
-
throw error;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
try {
|
|
75
|
-
this.progressService.start(goals, i18n.t('Initializing'));
|
|
76
|
-
const plaintext = await this.resourceModel.serializePlaintextDto(resource.resourceTypeId, plaintextDto);
|
|
77
|
-
|
|
78
|
-
// Encrypt and sign
|
|
79
|
-
await this.progressService.finishStep(i18n.t('Encrypting secret'), true);
|
|
80
|
-
const userId = User.getInstance().get().id;
|
|
81
|
-
const userPublicArmoredKey = this.keyring.findPublic(userId).armoredKey;
|
|
82
|
-
const userPublicKey = await OpenpgpAssertion.readKeyOrFail(userPublicArmoredKey);
|
|
83
|
-
const secret = await EncryptMessageService.encrypt(plaintext, userPublicKey, [privateKey]);
|
|
84
|
-
resource.secrets = new ResourceSecretsCollection([{data: secret}]);
|
|
85
|
-
|
|
86
|
-
// Save
|
|
87
|
-
await this.progressService.finishStep(i18n.t('Creating password'), true);
|
|
88
|
-
resource = await this.resourceModel.create(resource);
|
|
89
|
-
|
|
90
|
-
// Share if needed
|
|
91
|
-
if (resource.folderParentId) {
|
|
92
|
-
await this.handleCreateInFolder(resource, privateKey);
|
|
93
|
-
}
|
|
45
|
+
const resource = await this.exec(resourceDto, plaintextDto);
|
|
46
|
+
this.worker.port.emit(this.requestId, 'SUCCESS', resource);
|
|
94
47
|
} catch (error) {
|
|
95
48
|
console.error(error);
|
|
96
|
-
|
|
97
|
-
throw error;
|
|
49
|
+
this.worker.port.emit(this.requestId, 'ERROR', error);
|
|
98
50
|
}
|
|
99
|
-
|
|
100
|
-
await this.progressService.finishStep(i18n.t('Done!'), true);
|
|
101
|
-
await this.progressService.close();
|
|
102
|
-
|
|
103
|
-
return resource;
|
|
104
51
|
}
|
|
105
52
|
|
|
106
53
|
/**
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
* @param {ResourceEntity} resourceEntity
|
|
111
|
-
* @param {openpgp.PrivateKey} privateKey The user decrypted private key
|
|
54
|
+
* @param {object} resourceDto The resource data
|
|
55
|
+
* @param {string|object} plaintextDto The secret to encrypt
|
|
112
56
|
* @returns {Promise<void>}
|
|
113
57
|
*/
|
|
114
|
-
async
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
this.progressService.
|
|
124
|
-
|
|
125
|
-
// Sync keyring
|
|
126
|
-
await this.progressService.finishStep(i18n.t('Synchronizing keys'), true);
|
|
127
|
-
await this.keyring.sync();
|
|
128
|
-
|
|
129
|
-
// Share
|
|
130
|
-
await this.progressService.finishStep(i18n.t('Start sharing'), true);
|
|
131
|
-
const resourcesToShare = [resourceEntity.toDto({secrets: true})];
|
|
132
|
-
await this.shareModel.bulkShareResources(resourcesToShare, changes.toDto(), privateKey, async message =>
|
|
133
|
-
await this.progressService.finishStep(message)
|
|
134
|
-
);
|
|
135
|
-
await this.resourceModel.updateLocalStorage();
|
|
58
|
+
async exec(resourceDto, plaintextDto) {
|
|
59
|
+
try {
|
|
60
|
+
const goals = resourceDto.folder_parent_id ? 10 : 2;
|
|
61
|
+
const passphrase = await this.getPassphraseService.getPassphrase(this.worker);
|
|
62
|
+
this.progressService.start(goals, i18n.t('Initializing'));
|
|
63
|
+
const resourceCreated = await this.resourceCreateService.exec(resourceDto, plaintextDto, passphrase);
|
|
64
|
+
await this.progressService.finishStep(i18n.t('Done!'), true);
|
|
65
|
+
return resourceCreated;
|
|
66
|
+
} finally {
|
|
67
|
+
await this.progressService.close();
|
|
136
68
|
}
|
|
137
69
|
}
|
|
138
70
|
}
|
|
@@ -0,0 +1,120 @@
|
|
|
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 4.10.0
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import {defaultApiClientOptions} from "passbolt-styleguide/src/shared/lib/apiClient/apiClientOptions.test.data";
|
|
16
|
+
import ResourceCreateController from "./resourceCreateController";
|
|
17
|
+
import AccountEntity from "../../model/entity/account/accountEntity";
|
|
18
|
+
import {defaultAccountDto} from "../../model/entity/account/accountEntity.test.data";
|
|
19
|
+
import {defaultResourceDto, defaultResourceV4Dto} from "passbolt-styleguide/src/shared/models/entity/resource/resourceEntity.test.data";
|
|
20
|
+
import {pgpKeys} from "passbolt-styleguide/test/fixture/pgpKeys/keys";
|
|
21
|
+
import {enableFetchMocks} from "jest-fetch-mock";
|
|
22
|
+
import {mockApiResponse} from "../../../../../test/mocks/mockApiResponse";
|
|
23
|
+
import {v4 as uuidv4} from "uuid";
|
|
24
|
+
import EncryptMessageService from "../../service/crypto/encryptMessageService";
|
|
25
|
+
|
|
26
|
+
jest.mock("../../service/passphrase/getPassphraseService");
|
|
27
|
+
jest.mock("../../service/progress/progressService");
|
|
28
|
+
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
enableFetchMocks();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe("ResourceCreateController", () => {
|
|
34
|
+
let controller, worker;
|
|
35
|
+
const secret = "secret";
|
|
36
|
+
|
|
37
|
+
beforeEach(() => {
|
|
38
|
+
worker = {
|
|
39
|
+
port: {
|
|
40
|
+
emit: jest.fn()
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
const account = new AccountEntity(defaultAccountDto());
|
|
44
|
+
const apiClientOptions = defaultApiClientOptions();
|
|
45
|
+
controller = new ResourceCreateController(worker, null, apiClientOptions, account);
|
|
46
|
+
controller.getPassphraseService.getPassphrase.mockResolvedValue(pgpKeys.ada.passphrase);
|
|
47
|
+
fetch.doMockOnce(() => mockApiResponse(defaultResourceV4Dto()));
|
|
48
|
+
});
|
|
49
|
+
describe("AccountRecoveryLoginController::_exec", () => {
|
|
50
|
+
it("Should call the resourceCreateService and emit a success message", async() => {
|
|
51
|
+
expect.assertions(3);
|
|
52
|
+
|
|
53
|
+
const resourceDTO = defaultResourceDto();
|
|
54
|
+
jest.spyOn(controller.resourceCreateService, "exec").mockImplementationOnce(() => resourceDTO);
|
|
55
|
+
await controller._exec(resourceDTO, secret);
|
|
56
|
+
|
|
57
|
+
expect(controller.resourceCreateService.exec).toHaveBeenCalledTimes(1);
|
|
58
|
+
expect(controller.resourceCreateService.exec).toHaveBeenCalledWith(resourceDTO, secret, pgpKeys.ada.passphrase);
|
|
59
|
+
expect(controller.worker.port.emit).toHaveBeenCalledWith(null, 'SUCCESS', resourceDTO);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("Should call the resourceCreateService and emit an error message", async() => {
|
|
63
|
+
expect.assertions(1);
|
|
64
|
+
|
|
65
|
+
const error = new Error();
|
|
66
|
+
jest.spyOn(controller.resourceCreateService, "exec").mockImplementationOnce(() => { throw error; });
|
|
67
|
+
await controller._exec(defaultResourceDto(), null);
|
|
68
|
+
|
|
69
|
+
expect(controller.worker.port.emit).toHaveBeenCalledWith(null, 'ERROR', error);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
describe("AccountRecoveryLoginController::exec", () => {
|
|
73
|
+
it("Should call progress service without folder goals", async() => {
|
|
74
|
+
expect.assertions(2);
|
|
75
|
+
|
|
76
|
+
await controller.exec(defaultResourceDto(), secret);
|
|
77
|
+
expect(controller.progressService.start).toHaveBeenCalledTimes(1);
|
|
78
|
+
expect(controller.progressService.start).toHaveBeenCalledWith(2, 'Initializing');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("Should call progress service with folder goals", async() => {
|
|
82
|
+
expect.assertions(2);
|
|
83
|
+
const resource = defaultResourceDto({
|
|
84
|
+
folder_parent_id: uuidv4()
|
|
85
|
+
});
|
|
86
|
+
await controller.exec(resource, secret);
|
|
87
|
+
expect(controller.progressService.start).toHaveBeenCalledTimes(1);
|
|
88
|
+
expect(controller.progressService.start).toHaveBeenCalledWith(10, 'Initializing');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("Should throw an error if the user passphrase cannot be retrieved", async() => {
|
|
92
|
+
expect.assertions(1);
|
|
93
|
+
|
|
94
|
+
controller.getPassphraseService.getPassphrase.mockImplementation(() => { throw new Error("Cannot retrieve key"); });
|
|
95
|
+
const promise = controller.exec(defaultResourceDto(), secret);
|
|
96
|
+
await expect(promise).rejects.toThrowError("Cannot retrieve key");
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("Should close progressService when creation succeed", async() => {
|
|
100
|
+
expect.assertions(2);
|
|
101
|
+
|
|
102
|
+
await controller.exec(defaultResourceDto(), secret);
|
|
103
|
+
expect(controller.progressService.close).toHaveBeenCalledTimes(1);
|
|
104
|
+
expect(controller.progressService.close).toHaveBeenCalled();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("Should close progressService when creation failed", async() => {
|
|
108
|
+
expect.assertions(2);
|
|
109
|
+
|
|
110
|
+
jest.spyOn(EncryptMessageService, "encrypt").mockImplementation(() => Promise.reject("Cannot encrypt message"));
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
await controller.exec(defaultResourceDto(), secret);
|
|
114
|
+
} catch {
|
|
115
|
+
expect(controller.progressService.close).toHaveBeenCalledTimes(1);
|
|
116
|
+
expect(controller.progressService.close).toHaveBeenCalled();
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
});
|
|
@@ -204,7 +204,7 @@ class ShareFoldersController {
|
|
|
204
204
|
|
|
205
205
|
// Share resources
|
|
206
206
|
if (this.resourcesChanges.length) {
|
|
207
|
-
const resourcesDto = this.resources.toDto({secrets: true});
|
|
207
|
+
const resourcesDto = this.resources.toDto({secrets: true, metadata: true});
|
|
208
208
|
const changesDto = this.resourcesChanges.toDto();
|
|
209
209
|
await this.progressService.finishStep(i18n.t('Synchronizing keys'), true);
|
|
210
210
|
await this.keyring.sync();
|
|
@@ -91,14 +91,8 @@ const listen = function(worker, apiClientOptions, account) {
|
|
|
91
91
|
* @param plaintextDto {string|object} The plaintext data to encrypt
|
|
92
92
|
*/
|
|
93
93
|
worker.port.on('passbolt.resources.create', async(requestId, resourceDto, plaintextDto) => {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const savedResource = await controller.main(resourceDto, plaintextDto);
|
|
97
|
-
worker.port.emit(requestId, 'SUCCESS', savedResource);
|
|
98
|
-
} catch (error) {
|
|
99
|
-
console.error(error);
|
|
100
|
-
worker.port.emit(requestId, 'ERROR', error);
|
|
101
|
-
}
|
|
94
|
+
const controller = new ResourceCreateController(worker, requestId, apiClientOptions, account);
|
|
95
|
+
await controller._exec(resourceDto, plaintextDto);
|
|
102
96
|
});
|
|
103
97
|
|
|
104
98
|
/*
|
|
@@ -87,10 +87,18 @@ class ExportResourcesFileEntity extends Entity {
|
|
|
87
87
|
"resources_ids": {
|
|
88
88
|
"type": "array",
|
|
89
89
|
"nullable": true,
|
|
90
|
+
"items": {
|
|
91
|
+
"type": "string",
|
|
92
|
+
"format": "uuid"
|
|
93
|
+
}
|
|
90
94
|
},
|
|
91
95
|
"folders_ids": {
|
|
92
96
|
"type": "array",
|
|
93
97
|
"nullable": true,
|
|
98
|
+
"items": {
|
|
99
|
+
"type": "string",
|
|
100
|
+
"format": "uuid"
|
|
101
|
+
}
|
|
94
102
|
},
|
|
95
103
|
"export_resources": ExternalResourcesCollection.getSchema(),
|
|
96
104
|
"export_folders": ExternalFoldersCollection.getSchema(),
|