passbolt-browser-extension 5.10.4 → 5.11.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.
- package/.gitlab-ci/jobs/publish.yml +11 -1
- package/.gitlab-ci/scripts/bin/publish_npm.sh +1 -5
- package/.pre-commit-config.yaml +5 -0
- package/CHANGELOG.md +49 -0
- package/RELEASE_NOTES.md +93 -3
- package/build-safari-extension/Passbolt-Safari-Extension/Passbolt - password manager Extension/services/fetch/fetchService.swift +90 -2
- package/eslint.config.mjs +0 -6
- package/package.json +11 -13
- package/src/all/background_page/controller/auth/authLogoutController.js +11 -2
- package/src/all/background_page/controller/auth/authLogoutController.test.js +14 -5
- package/src/all/background_page/controller/group/groupCreateController.js +58 -0
- package/src/all/background_page/controller/group/groupCreateController.test.js +67 -0
- package/src/all/background_page/controller/tab/openResourceUriTabController.js +60 -0
- package/src/all/background_page/controller/tab/openResourceUriTabController.test.js +108 -0
- package/src/all/background_page/error/timeoutError.js +23 -0
- package/src/all/background_page/event/appEvents.js +12 -0
- package/src/all/background_page/event/groupEvents.js +3 -10
- package/src/all/background_page/model/entity/resource/resourceEntity.js +1 -1
- package/src/all/background_page/model/entity/sso/ssoLoginUrlEntity.js +2 -0
- package/src/all/background_page/model/entity/sso/ssoLoginUrlEntity.test.js +2 -0
- package/src/all/background_page/model/group/groupModel.js +0 -15
- package/src/all/background_page/service/api/group/groupApiService.js +6 -3
- package/src/all/background_page/service/auth/authVerifyLoginChallengeService.test.js +1 -1
- package/src/all/background_page/service/auth/decryptUserAuthTokenService.js +2 -2
- package/src/all/background_page/service/group/createGroupService.js +48 -0
- package/src/all/background_page/service/group/createGroupService.test.js +68 -0
- package/src/all/background_page/service/tab/tabService.js +37 -5
- package/src/all/background_page/service/tab/tabService.test.js +132 -1
- package/src/all/background_page/utils/format/formDataUtils.js +0 -16
- package/src/all/background_page/utils/format/formDataUtils.test.data.js +0 -7
- package/src/all/background_page/utils/format/formDataUtils.test.js +1 -39
- package/src/all/background_page/utils/promise/promiseTimeoutService.js +3 -1
- package/src/all/background_page/vendors/locutus/stripslashes.js +25 -0
- package/src/all/background_page/vendors/locutus/stripslashes.test.js +64 -0
- package/src/all/background_page/vendors/locutus/urldecode.js +21 -0
- package/src/all/background_page/vendors/locutus/urldecode.test.js +64 -0
- package/src/all/background_page/vendors/locutus/urlencode.test.data.js +23 -0
- package/src/all/contentScripts/js/app/App.js +11 -0
- package/src/all/contentScripts/js/app/Login.js +11 -0
- package/src/chrome/manifest.json +1 -1
- package/src/chrome-mv3/manifest.json +1 -1
- package/src/firefox/manifest.json +5 -2
- package/src/safari/common/polyfill/fetchPolyfill.js +1 -1
- package/src/safari/common/polyfill/fetchPolyfill.test.js +8 -4
- package/src/safari/manifest.json +1 -1
|
@@ -39,7 +39,17 @@ publish-edge:
|
|
|
39
39
|
|
|
40
40
|
publish-to-npmjs:
|
|
41
41
|
stage: publish
|
|
42
|
-
image: ${CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX}/node:
|
|
42
|
+
image: ${CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX}/node:24
|
|
43
|
+
id_tokens:
|
|
44
|
+
NPM_ID_TOKEN:
|
|
45
|
+
aud: "npm:registry.npmjs.org"
|
|
46
|
+
SIGSTORE_ID_TOKEN:
|
|
47
|
+
aud: sigstore
|
|
48
|
+
when: manual
|
|
49
|
+
resource_group: publish/npm
|
|
50
|
+
environment:
|
|
51
|
+
name: publish/npm
|
|
52
|
+
url: https://www.npmjs.com/package/passbolt-browser-extension
|
|
43
53
|
rules:
|
|
44
54
|
- if: "$CI_COMMIT_TAG"
|
|
45
55
|
script:
|
|
@@ -9,10 +9,6 @@ CI_SCRIPTS_DIR=$(dirname "$0")/..
|
|
|
9
9
|
# shellcheck source=.gitlab-ci/scripts/lib/version-check.sh
|
|
10
10
|
source "$CI_SCRIPTS_DIR"/lib/version-check.sh
|
|
11
11
|
|
|
12
|
-
echo //registry.npmjs.org/:_authToken="$NPM_PUBLISH_TOKEN" > .npmrc
|
|
13
|
-
echo email="$NPM_PUBLISH_EMAIL" >> .npmrc
|
|
14
|
-
echo always-auth=true >> .npmrc
|
|
15
|
-
|
|
16
12
|
if is_release_candidate "$CI_COMMIT_TAG"; then
|
|
17
13
|
npm publish --tag next
|
|
18
14
|
elif is_release_alpha "$CI_COMMIT_TAG"; then
|
|
@@ -23,4 +19,4 @@ elif is_stable "$CI_COMMIT_TAG"; then
|
|
|
23
19
|
npm publish
|
|
24
20
|
else
|
|
25
21
|
echo "The tag format is not supported"
|
|
26
|
-
fi
|
|
22
|
+
fi
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,55 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [5.11.0] - 2026-04-07
|
|
8
|
+
### Added
|
|
9
|
+
- PB-49733 SMTP-OAUTH - WP2.1 Update SmtpSettingsService to SmtpSettingsApiService
|
|
10
|
+
- PB-49734 SMTP-OAUTH - WP1.1 Create the SmtpSettingsEntity
|
|
11
|
+
- PB-49737 SMTP-OAUTH - WP2.2 Update SmtpTestSettingsService to SmtpTestSettingsApiService
|
|
12
|
+
- PB-49738 SMTP-OAUTH - WP2.3 Split SmtpSettingsModel to new architecture pattern
|
|
13
|
+
- PB-49739 SMTP-OAUTH - WP2.4 Split SmtpTestSettingsModel to new architecture pattern
|
|
14
|
+
- PB-49740 SMTP-OAUTH - WP3.1 Adapt context with the new SMTP entities
|
|
15
|
+
- PB-49741 SMTP-OAUTH - WP3.2 Adapt ManageSmtpAdministationSettings to handle the new OAUTH fields
|
|
16
|
+
- PB-50058 OAuth SMTP: add the new styleguide to backend
|
|
17
|
+
- PB-50135 SSO with PingOne
|
|
18
|
+
- PB-50157 Enable avatar upload for Safari
|
|
19
|
+
- PB-50254 SCIM-WP1.2 Adapt form to handle the new date field and display warning message when expired
|
|
20
|
+
- PB-50263 Add a username selector compatible with ProxMox
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
- PB-46678 Fix quickaccess closing issue on Safari
|
|
24
|
+
- PB-49237 DisplayUserBadgeMenu attention required should be displayed on Administration page served by API
|
|
25
|
+
- PB-49287 When deleting a user, the URL must changed not to reference the deleted user id
|
|
26
|
+
- PB-49476 Fix autofill for websites using identifier as name for username field
|
|
27
|
+
- PB-49619 Fix username input field selector for OVH
|
|
28
|
+
- PB-49849 Sync generator password policy with the administration after save
|
|
29
|
+
- PB-49866 Fix the expiry column in the resource workspace grid is not present anymore
|
|
30
|
+
- PB-49882 Fix username input field selector for Supermicro IPMI WebUI
|
|
31
|
+
- PB-50023 Fix multifield OTP selector matching hidden inputs
|
|
32
|
+
- PB-50077 Fix React router issue that reloads the page unexpectedly
|
|
33
|
+
- PB-50177 Fix autofill issues for two websites
|
|
34
|
+
|
|
35
|
+
### Maintenance
|
|
36
|
+
- PB-49129 Delegate tab opening to service worker in order to send all cookie via Safari
|
|
37
|
+
- PB-49459 Timeouts not cleared properly when filtering resources/users grids by keywords
|
|
38
|
+
- PB-49705 Add missing TOTP unit tests
|
|
39
|
+
- PB-49730 Setup an environment for publishing to npmjs registry
|
|
40
|
+
- PB-49998 Add required `data_collection_permissions` for Firefox and set it to `none`
|
|
41
|
+
- PB-50013 Make Safari download custom avatars
|
|
42
|
+
- PB-50118 Major upgrade for locutus (Critical) - passbolt-browser-extension
|
|
43
|
+
- PB-50158 Add Safari enablement through a feature flag
|
|
44
|
+
- PB-50200 Move the logic of passbolt.groups.create to GroupCreateController
|
|
45
|
+
- PB-50201 Update group create call in groupApiService to contain "my_group_user" as urlOptions
|
|
46
|
+
- PB-50202 Add supported formats documentation link in export dialog
|
|
47
|
+
- PB-50225 Create a CreateGroupService.js file and move the create call to api service inside it
|
|
48
|
+
- PB-50338 - Fix phantom @babel/preset-react
|
|
49
|
+
|
|
50
|
+
### Security
|
|
51
|
+
- PB-49608 Fix ReDoS vulnerability in PGP armor regex validation
|
|
52
|
+
- PB-50271 Fix GHSA-25h7-pfq9-p65f - HIGH CVSS3.1
|
|
53
|
+
- PB-50272 Fix brace-expansion vulnerabilities
|
|
54
|
+
|
|
55
|
+
|
|
7
56
|
## [5.9.0] - 2026-01-21
|
|
8
57
|
### Fixed
|
|
9
58
|
- PB-43511 Display the "Migrate metadata" admin home page card icon with a 2px stroke width
|
package/RELEASE_NOTES.md
CHANGED
|
@@ -1,7 +1,97 @@
|
|
|
1
|
-
Passbolt 5.
|
|
1
|
+
Passbolt 5.11.0 introduces improvements to enterprise authentication and integration capabilities, alongside continued security hardening.
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This release adds support for OAuth-based SMTP authentication for Microsoft Exchange Online and expands SSO coverage with PingOne. It also includes the finalisation of SCIM following external audit fixes.
|
|
4
|
+
|
|
5
|
+
## SMTP OAuth support for Microsoft Exchange Online
|
|
6
|
+
|
|
7
|
+
Passbolt 5.11 introduces OAuth 2.0 support for SMTP with Microsoft Exchange Online, replacing legacy username/password authentication.
|
|
8
|
+
|
|
9
|
+
Administrators can configure the OAuth (Client Credentials) method by registering an application in Microsoft Entra ID and providing the required tenant ID, client ID, client secret, and service account email.
|
|
10
|
+
|
|
11
|
+
At runtime, Passbolt retrieves short-lived access tokens to authenticate SMTP connections without user interaction, improving security and aligning with modern authentication standards.
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
## PingOne SSO support (Passbolt Pro)
|
|
15
|
+
|
|
16
|
+
Passbolt 5.11 adds support for PingOne as a new SSO provider, enabling organisations to authenticate users via their existing Ping Identity infrastructure.
|
|
17
|
+
|
|
18
|
+
The integration is based on OpenID Connect (OIDC) using the Authorization Code flow, with Passbolt delegating authentication to PingOne and receiving a verified user identity via ID tokens.
|
|
19
|
+
|
|
20
|
+
Administrators can configure PingOne from the SSO settings using the required environment ID, client ID, client secret, and base URL, with a dry-run option available to validate the setup before activation. Once enabled, users are redirected to PingOne for authentication and seamlessly logged into Passbolt, including during account recovery.
|
|
21
|
+
|
|
22
|
+
This addition expands Passbolt’s SSO coverage for enterprise environments and removes a key adoption blocker for organisations standardised on Ping Identity.
|
|
23
|
+
|
|
24
|
+
## SCIM: audit fixes and general availability (Passbolt Pro)
|
|
25
|
+
|
|
26
|
+
Following the external security audit conducted by Cure53, this release includes fixes addressing the identified findings in the SCIM provisioning implementation.
|
|
27
|
+
|
|
28
|
+
With these changes, SCIM is now considered stable and exits beta.
|
|
29
|
+
|
|
30
|
+
The audit-driven improvements strengthen validation, error handling, and overall robustness of the provisioning flow. SCIM is now ready for production use in environments requiring automated user lifecycle management.
|
|
31
|
+
|
|
32
|
+
## Security improvements
|
|
33
|
+
|
|
34
|
+
This release continues the ongoing security hardening effort across the platform.
|
|
35
|
+
|
|
36
|
+
In addition to the SCIM audit fixes, improvements have been made to align with external audit recommendations and reduce potential attack surface in authentication and integration layers.
|
|
37
|
+
|
|
38
|
+
## Maintenance & performance
|
|
39
|
+
|
|
40
|
+
This release includes general performance improvements, particularly around background job processing and email delivery workflows.
|
|
41
|
+
|
|
42
|
+
Email-related operations are now more efficient and better distributed, reducing bottlenecks in high-load environments.
|
|
43
|
+
|
|
44
|
+
As usual, additional optimisations are already in progress for upcoming releases.
|
|
45
|
+
|
|
46
|
+
## Conclusion
|
|
47
|
+
|
|
48
|
+
As usual, the release is also packed with additional improvements and fixes. Check out the changelog to learn more.
|
|
49
|
+
|
|
50
|
+
Many thanks to everyone who provided feedback, reported bugs, and contributed to making passbolt better!
|
|
51
|
+
|
|
52
|
+
### Added
|
|
53
|
+
- PB-49733 SMTP-OAUTH - WP2.1 Update SmtpSettingsService to SmtpSettingsApiService
|
|
54
|
+
- PB-49734 SMTP-OAUTH - WP1.1 Create the SmtpSettingsEntity
|
|
55
|
+
- PB-49737 SMTP-OAUTH - WP2.2 Update SmtpTestSettingsService to SmtpTestSettingsApiService
|
|
56
|
+
- PB-49738 SMTP-OAUTH - WP2.3 Split SmtpSettingsModel to new architecture pattern
|
|
57
|
+
- PB-49739 SMTP-OAUTH - WP2.4 Split SmtpTestSettingsModel to new architecture pattern
|
|
58
|
+
- PB-49740 SMTP-OAUTH - WP3.1 Adapt context with the new SMTP entities
|
|
59
|
+
- PB-49741 SMTP-OAUTH - WP3.2 Adapt ManageSmtpAdministationSettings to handle the new OAUTH fields
|
|
60
|
+
- PB-50058 OAuth SMTP: add the new styleguide to backend
|
|
61
|
+
- PB-50135 SSO with PingOne
|
|
62
|
+
- PB-50157 Enable avatar upload for Safari
|
|
63
|
+
- PB-50254 SCIM-WP1.2 Adapt form to handle the new date field and display warning message when expired
|
|
64
|
+
- PB-50263 Add a username selector compatible with ProxMox
|
|
4
65
|
|
|
5
66
|
### Fixed
|
|
6
|
-
- PB-
|
|
67
|
+
- PB-46678 Fix quickaccess closing issue on Safari
|
|
68
|
+
- PB-49237 DisplayUserBadgeMenu attention required should be displayed on Administration page served by API
|
|
69
|
+
- PB-49287 When deleting a user, the URL must changed not to reference the deleted user id
|
|
70
|
+
- PB-49476 Fix autofill for websites using identifier as name for username field
|
|
71
|
+
- PB-49619 Fix username input field selector for OVH
|
|
72
|
+
- PB-49849 Sync generator password policy with the administration after save
|
|
73
|
+
- PB-49866 Fix the expiry column in the resource workspace grid is not present anymore
|
|
74
|
+
- PB-49882 Fix username input field selector for Supermicro IPMI WebUI
|
|
75
|
+
- PB-50023 Fix multifield OTP selector matching hidden inputs
|
|
76
|
+
- PB-50077 Fix React router issue that reloads the page unexpectedly
|
|
77
|
+
- PB-50177 Fix autofill issues for two websites
|
|
78
|
+
|
|
79
|
+
### Maintenance
|
|
80
|
+
- PB-49129 Delegate tab opening to service worker in order to send all cookie via Safari
|
|
7
81
|
- PB-49459 Timeouts not cleared properly when filtering resources/users grids by keywords
|
|
82
|
+
- PB-49705 Add missing TOTP unit tests
|
|
83
|
+
- PB-49730 Setup an environment for publishing to npmjs registry
|
|
84
|
+
- PB-49998 Add required `data_collection_permissions` for Firefox and set it to `none`
|
|
85
|
+
- PB-50013 Make Safari download custom avatars
|
|
86
|
+
- PB-50118 Major upgrade for locutus (Critical) - passbolt-browser-extension
|
|
87
|
+
- PB-50158 Add Safari enablement through a feature flag
|
|
88
|
+
- PB-50200 Move the logic of passbolt.groups.create to GroupCreateController
|
|
89
|
+
- PB-50201 Update group create call in groupApiService to contain "my_group_user" as urlOptions
|
|
90
|
+
- PB-50202 Add supported formats documentation link in export dialog
|
|
91
|
+
- PB-50225 Create a CreateGroupService.js file and move the create call to api service inside it
|
|
92
|
+
- PB-50338 - Fix phantom @babel/preset-react
|
|
93
|
+
|
|
94
|
+
### Security
|
|
95
|
+
- PB-49608 Fix ReDoS vulnerability in PGP armor regex validation
|
|
96
|
+
- PB-50271 Fix GHSA-25h7-pfq9-p65f - HIGH CVSS3.1
|
|
97
|
+
- PB-50272 Fix brace-expansion vulnerabilities
|
|
@@ -23,6 +23,14 @@
|
|
|
23
23
|
|
|
24
24
|
import Foundation
|
|
25
25
|
|
|
26
|
+
private extension Data {
|
|
27
|
+
mutating func append(_ string: String) {
|
|
28
|
+
if let data = string.data(using: .utf8) {
|
|
29
|
+
append(data)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
26
34
|
final class FetchService {
|
|
27
35
|
|
|
28
36
|
// Runs a fetch on the API with profile isolation.
|
|
@@ -35,19 +43,31 @@ final class FetchService {
|
|
|
35
43
|
// profileUUID: The Safari profile UUID for session isolation
|
|
36
44
|
static func fetch(url: URL, options: [String: Any], profileUUID: String) async throws -> [String: Any] {
|
|
37
45
|
let method = options["method"] as? String ?? "GET"
|
|
38
|
-
let body = options["body"] as? String ?? ""
|
|
39
46
|
let headers = options["headers"] as? [String: String] ?? [:]
|
|
40
47
|
let cookies = options["cookies"] as? String
|
|
41
48
|
|
|
42
49
|
var httpRequest = URLRequest(url: url)
|
|
43
50
|
httpRequest.httpMethod = method
|
|
44
|
-
|
|
51
|
+
|
|
52
|
+
// Build the body: structured FormData array (may contain files) or plain string
|
|
53
|
+
if let formDataArray = options["body"] as? [[String: Any]] {
|
|
54
|
+
let boundary = UUID().uuidString
|
|
55
|
+
httpRequest.httpBody = buildMultipartBody(formDataArray: formDataArray, boundary: boundary)
|
|
56
|
+
httpRequest.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
|
|
57
|
+
} else {
|
|
58
|
+
let body = options["body"] as? String ?? ""
|
|
59
|
+
httpRequest.httpBody = body.data(using: .utf8)
|
|
60
|
+
}
|
|
45
61
|
|
|
46
62
|
// SECURITY: Add cache-control headers to prevent response caching
|
|
47
63
|
httpRequest.setValue("no-cache, no-store, must-revalidate", forHTTPHeaderField: "Cache-Control")
|
|
48
64
|
httpRequest.setValue("no-cache", forHTTPHeaderField: "Pragma")
|
|
49
65
|
|
|
50
66
|
for header in headers {
|
|
67
|
+
// Skip Content-Type when we already set it for multipart
|
|
68
|
+
if header.key.lowercased() == "content-type" && httpRequest.value(forHTTPHeaderField: "Content-Type")?.contains("multipart") == true {
|
|
69
|
+
continue
|
|
70
|
+
}
|
|
51
71
|
httpRequest.setValue(String(describing: header.value), forHTTPHeaderField: String(describing: header.key))
|
|
52
72
|
}
|
|
53
73
|
|
|
@@ -59,6 +79,74 @@ final class FetchService {
|
|
|
59
79
|
return try await doFetch(request: httpRequest, profileUUID: profileUUID)
|
|
60
80
|
}
|
|
61
81
|
|
|
82
|
+
/// Build a multipart/form-data body from the structured FormData array sent by JavaScript.
|
|
83
|
+
/// Each entry has: key, value, type ("SCALAR" or "FILE"), and optionally name (for files).
|
|
84
|
+
/// File values use the data URL format: "data:<mimeType>;base64,<base64Data>"
|
|
85
|
+
private static func buildMultipartBody(formDataArray: [[String: Any]], boundary: String) -> Data {
|
|
86
|
+
var body = Data()
|
|
87
|
+
|
|
88
|
+
for entry in formDataArray {
|
|
89
|
+
guard let key = entry["key"] as? String,
|
|
90
|
+
let value = entry["value"] as? String,
|
|
91
|
+
let type = entry["type"] as? String else {
|
|
92
|
+
continue
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
let sanitizedKey = sanitizeHeaderValue(key)
|
|
96
|
+
body.append("--\(boundary)\r\n")
|
|
97
|
+
|
|
98
|
+
if type == "FILE" {
|
|
99
|
+
let rawFilename = entry["name"] as? String ?? "file"
|
|
100
|
+
let sanitizedFilename = sanitizeFilename(rawFilename)
|
|
101
|
+
let (mimeType, fileData) = decodeDataURL(value)
|
|
102
|
+
let sanitizedMimeType = sanitizeHeaderValue(mimeType)
|
|
103
|
+
|
|
104
|
+
body.append("Content-Disposition: form-data; name=\"\(sanitizedKey)\"; filename=\"\(sanitizedFilename)\"\r\n")
|
|
105
|
+
body.append("Content-Type: \(sanitizedMimeType)\r\n\r\n")
|
|
106
|
+
body.append(fileData)
|
|
107
|
+
body.append("\r\n")
|
|
108
|
+
} else {
|
|
109
|
+
body.append("Content-Disposition: form-data; name=\"\(sanitizedKey)\"\r\n\r\n")
|
|
110
|
+
body.append("\(value)\r\n")
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
body.append("--\(boundary)--\r\n")
|
|
115
|
+
return body
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/// Sanitize a filename for use in Content-Disposition headers.
|
|
119
|
+
/// Strips path traversal components, quotes, and CRLF characters.
|
|
120
|
+
private static func sanitizeFilename(_ raw: String) -> String {
|
|
121
|
+
let nameOnly = raw.components(separatedBy: CharacterSet(charactersIn: "/\\")).last ?? "file"
|
|
122
|
+
return sanitizeHeaderValue(nameOnly)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/// Sanitize a value interpolated into an HTTP header by removing quotes and CRLF.
|
|
126
|
+
private static func sanitizeHeaderValue(_ raw: String) -> String {
|
|
127
|
+
raw.replacingOccurrences(of: "\"", with: "")
|
|
128
|
+
.replacingOccurrences(of: "\r", with: "")
|
|
129
|
+
.replacingOccurrences(of: "\n", with: "")
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/// Decode a data URL (e.g. "data:image/png;base64,iVBOR...") into its MIME type and binary data.
|
|
133
|
+
private static func decodeDataURL(_ dataURL: String) -> (mimeType: String, data: Data) {
|
|
134
|
+
let parts = dataURL.components(separatedBy: ",")
|
|
135
|
+
guard parts.count == 2,
|
|
136
|
+
let base64Data = Data(base64Encoded: parts[1]) else {
|
|
137
|
+
return ("application/octet-stream", Data())
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Extract MIME type from "data:image/png;base64"
|
|
141
|
+
let header = parts[0]
|
|
142
|
+
let mimeType = header
|
|
143
|
+
.replacingOccurrences(of: "data:", with: "")
|
|
144
|
+
.components(separatedBy: ";")
|
|
145
|
+
.first ?? "application/octet-stream"
|
|
146
|
+
|
|
147
|
+
return (mimeType, base64Data)
|
|
148
|
+
}
|
|
149
|
+
|
|
62
150
|
// The actual fetch sent to the API using a profile-isolated session
|
|
63
151
|
private static func doFetch(request: URLRequest, profileUUID: String) async throws -> [String: Any] {
|
|
64
152
|
// SECURITY: Use profile-specific session, NEVER URLSession.shared
|
package/eslint.config.mjs
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import globals from "globals";
|
|
2
|
-
import babelParser from "@babel/eslint-parser";
|
|
3
2
|
import path from "path";
|
|
4
3
|
import { fileURLToPath } from "url";
|
|
5
4
|
|
|
@@ -32,18 +31,13 @@ export default [
|
|
|
32
31
|
files: ["**/*.{js,jsx,mjs,cjs}"],
|
|
33
32
|
|
|
34
33
|
languageOptions: {
|
|
35
|
-
parser: babelParser,
|
|
36
34
|
ecmaVersion: 2024,
|
|
37
35
|
sourceType: "module",
|
|
38
36
|
|
|
39
37
|
parserOptions: {
|
|
40
|
-
requireConfigFile: false,
|
|
41
38
|
ecmaFeatures: {
|
|
42
39
|
jsx: true,
|
|
43
40
|
},
|
|
44
|
-
babelOptions: {
|
|
45
|
-
presets: ["@babel/preset-react"],
|
|
46
|
-
},
|
|
47
41
|
},
|
|
48
42
|
|
|
49
43
|
globals: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "passbolt-browser-extension",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.11.1",
|
|
4
4
|
"license": "AGPL-3.0",
|
|
5
5
|
"copyright": "Copyright 2025 Passbolt SA",
|
|
6
6
|
"description": "Passbolt web extension for the open source password manager for teams",
|
|
@@ -19,10 +19,9 @@
|
|
|
19
19
|
"ip-regex": "^5.0.0",
|
|
20
20
|
"jssha": "~3.3.1",
|
|
21
21
|
"kdbxweb": "2.1.1",
|
|
22
|
-
"locutus": "~2.0.39",
|
|
23
22
|
"openpgp": "^6.1.1",
|
|
24
23
|
"papaparse": "^5.5.2",
|
|
25
|
-
"passbolt-styleguide": "^5.
|
|
24
|
+
"passbolt-styleguide": "^5.11.3",
|
|
26
25
|
"react": "^18.3.1",
|
|
27
26
|
"react-dom": "^18.3.1",
|
|
28
27
|
"secrets-passbolt": "github:passbolt/secrets.js#v2.0.1",
|
|
@@ -33,31 +32,30 @@
|
|
|
33
32
|
},
|
|
34
33
|
"devDependencies": {
|
|
35
34
|
"@babel/core": "^7.23.2",
|
|
36
|
-
"@babel/eslint-parser": "^7.22.9",
|
|
37
35
|
"@babel/helpers": "^7.26.10",
|
|
38
36
|
"@babel/plugin-transform-runtime": "^7.22.9",
|
|
39
37
|
"@babel/preset-env": "^7.22.9",
|
|
40
|
-
"@babel/preset-react": "^7.
|
|
38
|
+
"@babel/preset-react": "^7.28.5",
|
|
41
39
|
"@babel/runtime": "^7.27.0",
|
|
42
40
|
"@babel/runtime-corejs3": "^7.26.10",
|
|
43
|
-
"@eslint/js": "^9.
|
|
41
|
+
"@eslint/js": "^9.39.4",
|
|
44
42
|
"@svgr/webpack": "^8.1.0",
|
|
45
43
|
"babel-jest": "^29.6.2",
|
|
46
44
|
"babel-loader": "^8.2.3",
|
|
47
45
|
"buffer": "^6.0.3",
|
|
48
46
|
"crx": "^5.0.1",
|
|
49
|
-
"eslint": "^9.
|
|
47
|
+
"eslint": "^9.39.4",
|
|
50
48
|
"eslint-config-prettier": "^10.1.8",
|
|
51
49
|
"eslint-import-resolver-alias": "^1.1.2",
|
|
52
50
|
"eslint-plugin-import": "^2.32.0",
|
|
53
|
-
"eslint-plugin-jest": "^29.
|
|
54
|
-
"eslint-plugin-n": "^17.
|
|
55
|
-
"eslint-plugin-no-unsanitized": "^4.1.
|
|
56
|
-
"eslint-plugin-prettier": "^5.5.
|
|
51
|
+
"eslint-plugin-jest": "^29.15.1",
|
|
52
|
+
"eslint-plugin-n": "^17.24.0",
|
|
53
|
+
"eslint-plugin-no-unsanitized": "^4.1.5",
|
|
54
|
+
"eslint-plugin-prettier": "^5.5.5",
|
|
57
55
|
"eslint-plugin-promise": "^7.2.1",
|
|
58
56
|
"eslint-plugin-react": "^7.37.5",
|
|
59
|
-
"eslint-plugin-regexp": "^
|
|
60
|
-
"eslint-plugin-security": "^
|
|
57
|
+
"eslint-plugin-regexp": "^3.1.0",
|
|
58
|
+
"eslint-plugin-security": "^4.0.0",
|
|
61
59
|
"filereader": "^0.10.3",
|
|
62
60
|
"formdata-node": "^6.0.3",
|
|
63
61
|
"globals": "^16.4.0",
|
|
@@ -54,8 +54,17 @@ class AuthLogoutController {
|
|
|
54
54
|
return;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
/*
|
|
58
|
+
* Use tabs.reload instead of tabs.update to ensure the page is fully
|
|
59
|
+
* reloaded from the server. A tabs.update navigation may restore the
|
|
60
|
+
* page from the browser's Back/Forward Cache (BFCache), resulting in
|
|
61
|
+
* a stale page with a dead extension message port after sign-out.
|
|
62
|
+
*
|
|
63
|
+
* See: PB-50644
|
|
64
|
+
*/
|
|
65
|
+
await browser.tabs.reload(this.worker.tab.id);
|
|
66
|
+
// const url = this.apiClientOptions.getBaseUrl().toString();
|
|
67
|
+
// await browser.tabs.update(this.worker.tab.id, { url });
|
|
59
68
|
}
|
|
60
69
|
}
|
|
61
70
|
|
|
@@ -34,7 +34,7 @@ describe("AuthLogoutController", () => {
|
|
|
34
34
|
});
|
|
35
35
|
|
|
36
36
|
it("Should sign-out the user and redirect after.", async () => {
|
|
37
|
-
expect.assertions(
|
|
37
|
+
expect.assertions(2);
|
|
38
38
|
const logoutSpy = jest.spyOn(AuthModel.prototype, "logout").mockImplementation(() => {});
|
|
39
39
|
|
|
40
40
|
const worker = {
|
|
@@ -48,10 +48,19 @@ describe("AuthLogoutController", () => {
|
|
|
48
48
|
await controller.exec(true);
|
|
49
49
|
|
|
50
50
|
expect(logoutSpy).toHaveBeenCalledTimes(1);
|
|
51
|
-
expect(browser.tabs.
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
expect(browser.tabs.reload).toHaveBeenCalledTimes(1);
|
|
52
|
+
/*
|
|
53
|
+
* Use tabs.reload instead of tabs.update to ensure the page is fully
|
|
54
|
+
* reloaded from the server. A tabs.update navigation may restore the
|
|
55
|
+
* page from the browser's Back/Forward Cache (BFCache), resulting in
|
|
56
|
+
* a stale page with a dead extension message port after sign-out.
|
|
57
|
+
*
|
|
58
|
+
* See: PB-50644
|
|
59
|
+
*/
|
|
60
|
+
// expect(browser.tabs.update).toHaveBeenCalledTimes(1);
|
|
61
|
+
// expect(browser.tabs.update).toHaveBeenCalledWith(worker.tab.id, {
|
|
62
|
+
// url: apiClientOptions.getBaseUrl().toString(),
|
|
63
|
+
// });
|
|
55
64
|
});
|
|
56
65
|
});
|
|
57
66
|
});
|
|
@@ -0,0 +1,58 @@
|
|
|
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.11.0
|
|
13
|
+
*/
|
|
14
|
+
import CreateGroupService from "../../service/group/createGroupService";
|
|
15
|
+
import GroupEntity from "../../model/entity/group/groupEntity";
|
|
16
|
+
|
|
17
|
+
class GroupCreateController {
|
|
18
|
+
/**
|
|
19
|
+
* GroupCreateController constructor
|
|
20
|
+
*
|
|
21
|
+
* @param {Worker} worker
|
|
22
|
+
* @param {string} requestId
|
|
23
|
+
* @param {ApiClientOptions} apiClientOptions the api client options
|
|
24
|
+
* @param {AccountEntity} account The account associated to the worker.
|
|
25
|
+
*/
|
|
26
|
+
constructor(worker, requestId, apiClientOptions, account) {
|
|
27
|
+
this.worker = worker;
|
|
28
|
+
this.requestId = requestId;
|
|
29
|
+
this.createGroupService = new CreateGroupService(apiClientOptions, account);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Controller executor.
|
|
34
|
+
* @param {object} groupDto The group data
|
|
35
|
+
* @returns {Promise<void>}
|
|
36
|
+
*/
|
|
37
|
+
async _exec(groupDto) {
|
|
38
|
+
try {
|
|
39
|
+
const group = await this.exec(groupDto);
|
|
40
|
+
this.worker.port.emit(this.requestId, "SUCCESS", group);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
console.error(error);
|
|
43
|
+
this.worker.port.emit(this.requestId, "ERROR", error);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Create a group.
|
|
49
|
+
* @param {object} groupDto The group data
|
|
50
|
+
* @returns {Promise<GroupEntity>}
|
|
51
|
+
*/
|
|
52
|
+
async exec(groupDto) {
|
|
53
|
+
const groupEntity = new GroupEntity(groupDto);
|
|
54
|
+
return this.createGroupService.create(groupEntity);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export default GroupCreateController;
|
|
@@ -0,0 +1,67 @@
|
|
|
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.11.0
|
|
13
|
+
*/
|
|
14
|
+
import AccountEntity from "../../model/entity/account/accountEntity";
|
|
15
|
+
import { defaultAccountDto } from "../../model/entity/account/accountEntity.test.data";
|
|
16
|
+
import { defaultApiClientOptions } from "passbolt-styleguide/src/shared/lib/apiClient/apiClientOptions.test.data";
|
|
17
|
+
import GroupCreateController from "./groupCreateController";
|
|
18
|
+
import { defaultGroupDto } from "passbolt-styleguide/src/shared/models/entity/group/groupEntity.test.data";
|
|
19
|
+
import GroupEntity from "../../model/entity/group/groupEntity";
|
|
20
|
+
|
|
21
|
+
describe("GroupCreateController", () => {
|
|
22
|
+
let controller, account, apiClientOptions;
|
|
23
|
+
|
|
24
|
+
beforeEach(async () => {
|
|
25
|
+
account = new AccountEntity(defaultAccountDto());
|
|
26
|
+
apiClientOptions = defaultApiClientOptions();
|
|
27
|
+
controller = new GroupCreateController(null, null, apiClientOptions, account);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe("GroupCreateController::exec", () => {
|
|
31
|
+
it("Should create a group via the service.", async () => {
|
|
32
|
+
expect.assertions(2);
|
|
33
|
+
|
|
34
|
+
const groupDto = defaultGroupDto({}, { withGroupsUsers: true, withMyGroupUser: true });
|
|
35
|
+
const createdGroupEntity = new GroupEntity(groupDto);
|
|
36
|
+
|
|
37
|
+
jest.spyOn(controller.createGroupService, "create").mockResolvedValue(createdGroupEntity);
|
|
38
|
+
|
|
39
|
+
const result = await controller.exec(groupDto);
|
|
40
|
+
|
|
41
|
+
expect(result).toBeInstanceOf(GroupEntity);
|
|
42
|
+
expect(result.name).toEqual(groupDto.name);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("Should call the service with a GroupEntity.", async () => {
|
|
46
|
+
expect.assertions(2);
|
|
47
|
+
|
|
48
|
+
const groupDto = defaultGroupDto({}, { withGroupsUsers: true, withMyGroupUser: true });
|
|
49
|
+
const createdGroupEntity = new GroupEntity(groupDto);
|
|
50
|
+
|
|
51
|
+
jest.spyOn(controller.createGroupService, "create").mockResolvedValue(createdGroupEntity);
|
|
52
|
+
|
|
53
|
+
await controller.exec(groupDto);
|
|
54
|
+
|
|
55
|
+
expect(controller.createGroupService.create).toHaveBeenCalledTimes(1);
|
|
56
|
+
expect(controller.createGroupService.create).toHaveBeenCalledWith(expect.any(GroupEntity));
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("Should throw if the group dto is invalid.", async () => {
|
|
60
|
+
expect.assertions(1);
|
|
61
|
+
|
|
62
|
+
const groupDto = { name: "" };
|
|
63
|
+
|
|
64
|
+
await expect(controller.exec(groupDto)).rejects.toThrow();
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
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.11.0
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import sanitizeUrl, { urlProtocols } from "passbolt-styleguide/src/react-extension/lib/Sanitize/sanitizeUrl";
|
|
16
|
+
import BrowserTabService from "../../service/ui/browserTab.service";
|
|
17
|
+
|
|
18
|
+
export default class OpenResourceUriTabController {
|
|
19
|
+
/**
|
|
20
|
+
* @constructor
|
|
21
|
+
* @param {Worker} worker The associated worker.
|
|
22
|
+
* @param {string} requestId The associated request id.
|
|
23
|
+
*/
|
|
24
|
+
constructor(worker, requestId) {
|
|
25
|
+
this.worker = worker;
|
|
26
|
+
this.requestId = requestId;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Wrapper of exec function to run it with worker.
|
|
31
|
+
* @return {Promise<void>}
|
|
32
|
+
*/
|
|
33
|
+
async _exec() {
|
|
34
|
+
try {
|
|
35
|
+
await this.exec.apply(this, arguments);
|
|
36
|
+
this.worker.port.emit(this.requestId, "SUCCESS");
|
|
37
|
+
} catch (error) {
|
|
38
|
+
console.error(error);
|
|
39
|
+
this.worker.port.emit(this.requestId, "ERROR", error);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Opens the given url in a new tab
|
|
45
|
+
* @param {string} uriString the URI to try to open on a new tab
|
|
46
|
+
* @returns {Promise<void>}
|
|
47
|
+
*/
|
|
48
|
+
async exec(urlString) {
|
|
49
|
+
const url = sanitizeUrl(urlString, {
|
|
50
|
+
whiteListedProtocols: [urlProtocols.HTTPS, urlProtocols.HTTP],
|
|
51
|
+
defaultProtocol: urlProtocols.HTTPS,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
if (!url) {
|
|
55
|
+
throw new Error("The given URL is not valid for opening in a new tab.");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
await BrowserTabService.openTab(url);
|
|
59
|
+
}
|
|
60
|
+
}
|