diff --git a/apps/browser/src/_locales/ar/messages.json b/apps/browser/src/_locales/ar/messages.json index 82639f19974..10443fcf449 100644 --- a/apps/browser/src/_locales/ar/messages.json +++ b/apps/browser/src/_locales/ar/messages.json @@ -5714,5 +5714,9 @@ }, "confirmKeyConnectorDomain": { "message": "Confirm Key Connector domain" + }, + "settingDisabledByPolicy": { + "message": "This setting is disabled by your organization's policy.", + "description": "This hint text is displayed when a user setting is disabled due to an organization policy." } } diff --git a/apps/browser/src/_locales/az/messages.json b/apps/browser/src/_locales/az/messages.json index 1d5ca88f40e..ad44440a343 100644 --- a/apps/browser/src/_locales/az/messages.json +++ b/apps/browser/src/_locales/az/messages.json @@ -5714,5 +5714,9 @@ }, "confirmKeyConnectorDomain": { "message": "Key Connector domenini təsdiqlə" + }, + "settingDisabledByPolicy": { + "message": "Bu ayar, təşkilatınızın siyasəti tərəfindən sıradan çıxarılıb.", + "description": "This hint text is displayed when a user setting is disabled due to an organization policy." } } diff --git a/apps/browser/src/_locales/be/messages.json b/apps/browser/src/_locales/be/messages.json index 60fa6e22481..e9ec6a06b8c 100644 --- a/apps/browser/src/_locales/be/messages.json +++ b/apps/browser/src/_locales/be/messages.json @@ -5714,5 +5714,9 @@ }, "confirmKeyConnectorDomain": { "message": "Confirm Key Connector domain" + }, + "settingDisabledByPolicy": { + "message": "This setting is disabled by your organization's policy.", + "description": "This hint text is displayed when a user setting is disabled due to an organization policy." } } diff --git a/apps/browser/src/_locales/bg/messages.json b/apps/browser/src/_locales/bg/messages.json index fdeb336d94f..942a2f489a0 100644 --- a/apps/browser/src/_locales/bg/messages.json +++ b/apps/browser/src/_locales/bg/messages.json @@ -5714,5 +5714,9 @@ }, "confirmKeyConnectorDomain": { "message": "Потвърждаване на домейна на конектора за ключове" + }, + "settingDisabledByPolicy": { + "message": "Тази настройка е изключена съгласно политиката на организацията Ви.", + "description": "This hint text is displayed when a user setting is disabled due to an organization policy." } } diff --git a/apps/browser/src/_locales/bn/messages.json b/apps/browser/src/_locales/bn/messages.json index 9baa0547ffb..8b8b89d45a2 100644 --- a/apps/browser/src/_locales/bn/messages.json +++ b/apps/browser/src/_locales/bn/messages.json @@ -5714,5 +5714,9 @@ }, "confirmKeyConnectorDomain": { "message": "Confirm Key Connector domain" + }, + "settingDisabledByPolicy": { + "message": "This setting is disabled by your organization's policy.", + "description": "This hint text is displayed when a user setting is disabled due to an organization policy." } } diff --git a/apps/browser/src/_locales/bs/messages.json b/apps/browser/src/_locales/bs/messages.json index 402c278f1f9..914b8700d13 100644 --- a/apps/browser/src/_locales/bs/messages.json +++ b/apps/browser/src/_locales/bs/messages.json @@ -5714,5 +5714,9 @@ }, "confirmKeyConnectorDomain": { "message": "Confirm Key Connector domain" + }, + "settingDisabledByPolicy": { + "message": "This setting is disabled by your organization's policy.", + "description": "This hint text is displayed when a user setting is disabled due to an organization policy." } } diff --git a/apps/browser/src/_locales/ca/messages.json b/apps/browser/src/_locales/ca/messages.json index 255d2bcb1b2..8f3a0ca386d 100644 --- a/apps/browser/src/_locales/ca/messages.json +++ b/apps/browser/src/_locales/ca/messages.json @@ -5714,5 +5714,9 @@ }, "confirmKeyConnectorDomain": { "message": "Confirm Key Connector domain" + }, + "settingDisabledByPolicy": { + "message": "This setting is disabled by your organization's policy.", + "description": "This hint text is displayed when a user setting is disabled due to an organization policy." } } diff --git a/apps/browser/src/_locales/cs/messages.json b/apps/browser/src/_locales/cs/messages.json index 9e08cf87e7a..db522f3aa4e 100644 --- a/apps/browser/src/_locales/cs/messages.json +++ b/apps/browser/src/_locales/cs/messages.json @@ -5714,5 +5714,9 @@ }, "confirmKeyConnectorDomain": { "message": "Potvrdit doménu Key Connectoru" + }, + "settingDisabledByPolicy": { + "message": "Toto nastavení je zakázáno zásadami Vaší organizace.", + "description": "This hint text is displayed when a user setting is disabled due to an organization policy." } } diff --git a/apps/browser/src/_locales/cy/messages.json b/apps/browser/src/_locales/cy/messages.json index 70f0163d349..eacbb06fd53 100644 --- a/apps/browser/src/_locales/cy/messages.json +++ b/apps/browser/src/_locales/cy/messages.json @@ -5714,5 +5714,9 @@ }, "confirmKeyConnectorDomain": { "message": "Confirm Key Connector domain" + }, + "settingDisabledByPolicy": { + "message": "This setting is disabled by your organization's policy.", + "description": "This hint text is displayed when a user setting is disabled due to an organization policy." } } diff --git a/apps/browser/src/_locales/da/messages.json b/apps/browser/src/_locales/da/messages.json index d65e8382434..ddc6f33599f 100644 --- a/apps/browser/src/_locales/da/messages.json +++ b/apps/browser/src/_locales/da/messages.json @@ -5714,5 +5714,9 @@ }, "confirmKeyConnectorDomain": { "message": "Confirm Key Connector domain" + }, + "settingDisabledByPolicy": { + "message": "This setting is disabled by your organization's policy.", + "description": "This hint text is displayed when a user setting is disabled due to an organization policy." } } diff --git a/apps/browser/src/_locales/de/messages.json b/apps/browser/src/_locales/de/messages.json index e41187db557..8878f4b698e 100644 --- a/apps/browser/src/_locales/de/messages.json +++ b/apps/browser/src/_locales/de/messages.json @@ -5580,30 +5580,30 @@ "message": "Willkommen in deinem Tresor!" }, "phishingPageTitleV2": { - "message": "Phishing attempt detected" + "message": "Phishing-Versuch erkannt" }, "phishingPageSummary": { - "message": "The site you are attempting to visit is a known malicious site and a security risk." + "message": "Die Website, die du versuchst zu öffnen, ist eine bekannte böswillige Website und ein Sicherheitsrisiko." }, "phishingPageCloseTabV2": { - "message": "Close this tab" + "message": "Diesen Tab schließen" }, "phishingPageContinueV2": { - "message": "Continue to this site (not recommended)" + "message": "Weiter zu dieser Seite (nicht empfohlen)" }, "phishingPageExplanation1": { - "message": "This site was found in ", + "message": "Diese Seite wurde in ", "description": "This is in multiple parts to allow for bold text in the middle of the sentence. A proper name follows this." }, "phishingPageExplanation2": { - "message": ", an open-source list of known phishing sites used for stealing personal and sensitive information.", + "message": " gefunden, einer Open-Source-Liste bekannter Phishing-Seiten, die zum Diebstahl persönlicher und vertraulicher Informationen verwendet werden.", "description": "This is in multiple parts to allow for bold text in the middle of the sentence. A proper name precedes this." }, "phishingPageLearnMore": { - "message": "Learn more about phishing detection" + "message": "Erfahre mehr über die Phishing-Erkennung" }, "protectedBy": { - "message": "Protected by $PRODUCT$", + "message": "Geschützt durch $PRODUCT$", "placeholders": { "product": { "content": "$1", @@ -5714,5 +5714,9 @@ }, "confirmKeyConnectorDomain": { "message": "Key Connector-Domain bestätigen" + }, + "settingDisabledByPolicy": { + "message": "Diese Einstellung ist durch die Richtlinien deiner Organisation deaktiviert.", + "description": "This hint text is displayed when a user setting is disabled due to an organization policy." } } diff --git a/apps/browser/src/_locales/el/messages.json b/apps/browser/src/_locales/el/messages.json index 5fbf848c299..7f519130df0 100644 --- a/apps/browser/src/_locales/el/messages.json +++ b/apps/browser/src/_locales/el/messages.json @@ -5714,5 +5714,9 @@ }, "confirmKeyConnectorDomain": { "message": "Confirm Key Connector domain" + }, + "settingDisabledByPolicy": { + "message": "This setting is disabled by your organization's policy.", + "description": "This hint text is displayed when a user setting is disabled due to an organization policy." } } diff --git a/apps/browser/src/_locales/en_GB/messages.json b/apps/browser/src/_locales/en_GB/messages.json index c3cb86b41e9..1b78e39ecf8 100644 --- a/apps/browser/src/_locales/en_GB/messages.json +++ b/apps/browser/src/_locales/en_GB/messages.json @@ -5714,5 +5714,9 @@ }, "confirmKeyConnectorDomain": { "message": "Confirm Key Connector domain" + }, + "settingDisabledByPolicy": { + "message": "This setting is disabled by your organisation's policy.", + "description": "This hint text is displayed when a user setting is disabled due to an organization policy." } } diff --git a/apps/browser/src/_locales/en_IN/messages.json b/apps/browser/src/_locales/en_IN/messages.json index b8c2905f8da..e7c3a197c75 100644 --- a/apps/browser/src/_locales/en_IN/messages.json +++ b/apps/browser/src/_locales/en_IN/messages.json @@ -5714,5 +5714,9 @@ }, "confirmKeyConnectorDomain": { "message": "Confirm Key Connector domain" + }, + "settingDisabledByPolicy": { + "message": "This setting is disabled by your organisation's policy.", + "description": "This hint text is displayed when a user setting is disabled due to an organization policy." } } diff --git a/apps/browser/src/_locales/es/messages.json b/apps/browser/src/_locales/es/messages.json index c63ba159700..cc3242fd4a9 100644 --- a/apps/browser/src/_locales/es/messages.json +++ b/apps/browser/src/_locales/es/messages.json @@ -5714,5 +5714,9 @@ }, "confirmKeyConnectorDomain": { "message": "Confirm Key Connector domain" + }, + "settingDisabledByPolicy": { + "message": "This setting is disabled by your organization's policy.", + "description": "This hint text is displayed when a user setting is disabled due to an organization policy." } } diff --git a/apps/browser/src/_locales/et/messages.json b/apps/browser/src/_locales/et/messages.json index 9182a97ace9..96adaeba324 100644 --- a/apps/browser/src/_locales/et/messages.json +++ b/apps/browser/src/_locales/et/messages.json @@ -5714,5 +5714,9 @@ }, "confirmKeyConnectorDomain": { "message": "Confirm Key Connector domain" + }, + "settingDisabledByPolicy": { + "message": "This setting is disabled by your organization's policy.", + "description": "This hint text is displayed when a user setting is disabled due to an organization policy." } } diff --git a/apps/browser/src/_locales/eu/messages.json b/apps/browser/src/_locales/eu/messages.json index 1432a4a163e..ab1d3f8ef8e 100644 --- a/apps/browser/src/_locales/eu/messages.json +++ b/apps/browser/src/_locales/eu/messages.json @@ -5714,5 +5714,9 @@ }, "confirmKeyConnectorDomain": { "message": "Confirm Key Connector domain" + }, + "settingDisabledByPolicy": { + "message": "This setting is disabled by your organization's policy.", + "description": "This hint text is displayed when a user setting is disabled due to an organization policy." } } diff --git a/apps/browser/src/_locales/fa/messages.json b/apps/browser/src/_locales/fa/messages.json index e7366161956..8d76ec3f428 100644 --- a/apps/browser/src/_locales/fa/messages.json +++ b/apps/browser/src/_locales/fa/messages.json @@ -5714,5 +5714,9 @@ }, "confirmKeyConnectorDomain": { "message": "Confirm Key Connector domain" + }, + "settingDisabledByPolicy": { + "message": "This setting is disabled by your organization's policy.", + "description": "This hint text is displayed when a user setting is disabled due to an organization policy." } } diff --git a/apps/browser/src/_locales/fi/messages.json b/apps/browser/src/_locales/fi/messages.json index f810ea1c725..b782c7e11af 100644 --- a/apps/browser/src/_locales/fi/messages.json +++ b/apps/browser/src/_locales/fi/messages.json @@ -5714,5 +5714,9 @@ }, "confirmKeyConnectorDomain": { "message": "Confirm Key Connector domain" + }, + "settingDisabledByPolicy": { + "message": "This setting is disabled by your organization's policy.", + "description": "This hint text is displayed when a user setting is disabled due to an organization policy." } } diff --git a/apps/browser/src/_locales/fil/messages.json b/apps/browser/src/_locales/fil/messages.json index 03b405805e6..6b85bdf8f43 100644 --- a/apps/browser/src/_locales/fil/messages.json +++ b/apps/browser/src/_locales/fil/messages.json @@ -5714,5 +5714,9 @@ }, "confirmKeyConnectorDomain": { "message": "Confirm Key Connector domain" + }, + "settingDisabledByPolicy": { + "message": "This setting is disabled by your organization's policy.", + "description": "This hint text is displayed when a user setting is disabled due to an organization policy." } } diff --git a/apps/browser/src/_locales/fr/messages.json b/apps/browser/src/_locales/fr/messages.json index 0eb29428ee9..a4518b54afc 100644 --- a/apps/browser/src/_locales/fr/messages.json +++ b/apps/browser/src/_locales/fr/messages.json @@ -5714,5 +5714,9 @@ }, "confirmKeyConnectorDomain": { "message": "Confirmez le domaine de Key Connector" + }, + "settingDisabledByPolicy": { + "message": "This setting is disabled by your organization's policy.", + "description": "This hint text is displayed when a user setting is disabled due to an organization policy." } } diff --git a/apps/browser/src/_locales/gl/messages.json b/apps/browser/src/_locales/gl/messages.json index b03f83272a0..66f459d97b7 100644 --- a/apps/browser/src/_locales/gl/messages.json +++ b/apps/browser/src/_locales/gl/messages.json @@ -5714,5 +5714,9 @@ }, "confirmKeyConnectorDomain": { "message": "Confirm Key Connector domain" + }, + "settingDisabledByPolicy": { + "message": "This setting is disabled by your organization's policy.", + "description": "This hint text is displayed when a user setting is disabled due to an organization policy." } } diff --git a/apps/browser/src/_locales/he/messages.json b/apps/browser/src/_locales/he/messages.json index 953f7ae1150..5243fa03283 100644 --- a/apps/browser/src/_locales/he/messages.json +++ b/apps/browser/src/_locales/he/messages.json @@ -5714,5 +5714,9 @@ }, "confirmKeyConnectorDomain": { "message": "אשר דומיין של Key Connector" + }, + "settingDisabledByPolicy": { + "message": "This setting is disabled by your organization's policy.", + "description": "This hint text is displayed when a user setting is disabled due to an organization policy." } } diff --git a/apps/browser/src/_locales/hi/messages.json b/apps/browser/src/_locales/hi/messages.json index d041cbd23ed..e887f573ba9 100644 --- a/apps/browser/src/_locales/hi/messages.json +++ b/apps/browser/src/_locales/hi/messages.json @@ -5714,5 +5714,9 @@ }, "confirmKeyConnectorDomain": { "message": "Confirm Key Connector domain" + }, + "settingDisabledByPolicy": { + "message": "This setting is disabled by your organization's policy.", + "description": "This hint text is displayed when a user setting is disabled due to an organization policy." } } diff --git a/apps/browser/src/_locales/hr/messages.json b/apps/browser/src/_locales/hr/messages.json index 4b5bd0f8612..a4441a9a142 100644 --- a/apps/browser/src/_locales/hr/messages.json +++ b/apps/browser/src/_locales/hr/messages.json @@ -5714,5 +5714,9 @@ }, "confirmKeyConnectorDomain": { "message": "Potvrdi domenu kontektora ključa" + }, + "settingDisabledByPolicy": { + "message": "This setting is disabled by your organization's policy.", + "description": "This hint text is displayed when a user setting is disabled due to an organization policy." } } diff --git a/apps/browser/src/_locales/hu/messages.json b/apps/browser/src/_locales/hu/messages.json index c6726c5619d..2f3d46f78d8 100644 --- a/apps/browser/src/_locales/hu/messages.json +++ b/apps/browser/src/_locales/hu/messages.json @@ -5714,5 +5714,9 @@ }, "confirmKeyConnectorDomain": { "message": "A Key Connector tartomány megerősítése" + }, + "settingDisabledByPolicy": { + "message": "Ezt a beállítást a szervezet házirendje letiltotta.", + "description": "This hint text is displayed when a user setting is disabled due to an organization policy." } } diff --git a/apps/browser/src/_locales/id/messages.json b/apps/browser/src/_locales/id/messages.json index 9a3830111c5..ccd332b9c1b 100644 --- a/apps/browser/src/_locales/id/messages.json +++ b/apps/browser/src/_locales/id/messages.json @@ -5714,5 +5714,9 @@ }, "confirmKeyConnectorDomain": { "message": "Confirm Key Connector domain" + }, + "settingDisabledByPolicy": { + "message": "This setting is disabled by your organization's policy.", + "description": "This hint text is displayed when a user setting is disabled due to an organization policy." } } diff --git a/apps/browser/src/_locales/it/messages.json b/apps/browser/src/_locales/it/messages.json index 6e2899ddc38..10b1c678826 100644 --- a/apps/browser/src/_locales/it/messages.json +++ b/apps/browser/src/_locales/it/messages.json @@ -5714,5 +5714,9 @@ }, "confirmKeyConnectorDomain": { "message": "Conferma dominio Key Connector" + }, + "settingDisabledByPolicy": { + "message": "This setting is disabled by your organization's policy.", + "description": "This hint text is displayed when a user setting is disabled due to an organization policy." } } diff --git a/apps/browser/src/_locales/ja/messages.json b/apps/browser/src/_locales/ja/messages.json index 1f0711dd9d5..7c9d9e80ed4 100644 --- a/apps/browser/src/_locales/ja/messages.json +++ b/apps/browser/src/_locales/ja/messages.json @@ -5714,5 +5714,9 @@ }, "confirmKeyConnectorDomain": { "message": "Confirm Key Connector domain" + }, + "settingDisabledByPolicy": { + "message": "This setting is disabled by your organization's policy.", + "description": "This hint text is displayed when a user setting is disabled due to an organization policy." } } diff --git a/apps/browser/src/_locales/ka/messages.json b/apps/browser/src/_locales/ka/messages.json index fa40f16db71..3b3189acd6d 100644 --- a/apps/browser/src/_locales/ka/messages.json +++ b/apps/browser/src/_locales/ka/messages.json @@ -5714,5 +5714,9 @@ }, "confirmKeyConnectorDomain": { "message": "Confirm Key Connector domain" + }, + "settingDisabledByPolicy": { + "message": "This setting is disabled by your organization's policy.", + "description": "This hint text is displayed when a user setting is disabled due to an organization policy." } } diff --git a/apps/browser/src/_locales/km/messages.json b/apps/browser/src/_locales/km/messages.json index db205f4740b..026e24dbd3a 100644 --- a/apps/browser/src/_locales/km/messages.json +++ b/apps/browser/src/_locales/km/messages.json @@ -5714,5 +5714,9 @@ }, "confirmKeyConnectorDomain": { "message": "Confirm Key Connector domain" + }, + "settingDisabledByPolicy": { + "message": "This setting is disabled by your organization's policy.", + "description": "This hint text is displayed when a user setting is disabled due to an organization policy." } } diff --git a/apps/browser/src/_locales/kn/messages.json b/apps/browser/src/_locales/kn/messages.json index 5cff99b20db..42a5c4f1b05 100644 --- a/apps/browser/src/_locales/kn/messages.json +++ b/apps/browser/src/_locales/kn/messages.json @@ -5714,5 +5714,9 @@ }, "confirmKeyConnectorDomain": { "message": "Confirm Key Connector domain" + }, + "settingDisabledByPolicy": { + "message": "This setting is disabled by your organization's policy.", + "description": "This hint text is displayed when a user setting is disabled due to an organization policy." } } diff --git a/apps/browser/src/_locales/ko/messages.json b/apps/browser/src/_locales/ko/messages.json index edb56940dc2..b17055c72d0 100644 --- a/apps/browser/src/_locales/ko/messages.json +++ b/apps/browser/src/_locales/ko/messages.json @@ -5714,5 +5714,9 @@ }, "confirmKeyConnectorDomain": { "message": "Confirm Key Connector domain" + }, + "settingDisabledByPolicy": { + "message": "This setting is disabled by your organization's policy.", + "description": "This hint text is displayed when a user setting is disabled due to an organization policy." } } diff --git a/apps/browser/src/_locales/lt/messages.json b/apps/browser/src/_locales/lt/messages.json index 7e99a52a5e1..4811d7585ed 100644 --- a/apps/browser/src/_locales/lt/messages.json +++ b/apps/browser/src/_locales/lt/messages.json @@ -5714,5 +5714,9 @@ }, "confirmKeyConnectorDomain": { "message": "Confirm Key Connector domain" + }, + "settingDisabledByPolicy": { + "message": "This setting is disabled by your organization's policy.", + "description": "This hint text is displayed when a user setting is disabled due to an organization policy." } } diff --git a/apps/browser/src/_locales/lv/messages.json b/apps/browser/src/_locales/lv/messages.json index 6bd32684f57..ce2ffa00c40 100644 --- a/apps/browser/src/_locales/lv/messages.json +++ b/apps/browser/src/_locales/lv/messages.json @@ -5714,5 +5714,9 @@ }, "confirmKeyConnectorDomain": { "message": "Apstiprināt Key Connector domēnu" + }, + "settingDisabledByPolicy": { + "message": "Šis iestatījums ir atspējots apvienības pamatnostādnēs.", + "description": "This hint text is displayed when a user setting is disabled due to an organization policy." } } diff --git a/apps/browser/src/_locales/ml/messages.json b/apps/browser/src/_locales/ml/messages.json index 8c53bac93db..4641fc0416b 100644 --- a/apps/browser/src/_locales/ml/messages.json +++ b/apps/browser/src/_locales/ml/messages.json @@ -5714,5 +5714,9 @@ }, "confirmKeyConnectorDomain": { "message": "Confirm Key Connector domain" + }, + "settingDisabledByPolicy": { + "message": "This setting is disabled by your organization's policy.", + "description": "This hint text is displayed when a user setting is disabled due to an organization policy." } } diff --git a/apps/browser/src/_locales/mr/messages.json b/apps/browser/src/_locales/mr/messages.json index d40a7ee8ee7..40370d4b980 100644 --- a/apps/browser/src/_locales/mr/messages.json +++ b/apps/browser/src/_locales/mr/messages.json @@ -5714,5 +5714,9 @@ }, "confirmKeyConnectorDomain": { "message": "Confirm Key Connector domain" + }, + "settingDisabledByPolicy": { + "message": "This setting is disabled by your organization's policy.", + "description": "This hint text is displayed when a user setting is disabled due to an organization policy." } } diff --git a/apps/browser/src/_locales/my/messages.json b/apps/browser/src/_locales/my/messages.json index db205f4740b..026e24dbd3a 100644 --- a/apps/browser/src/_locales/my/messages.json +++ b/apps/browser/src/_locales/my/messages.json @@ -5714,5 +5714,9 @@ }, "confirmKeyConnectorDomain": { "message": "Confirm Key Connector domain" + }, + "settingDisabledByPolicy": { + "message": "This setting is disabled by your organization's policy.", + "description": "This hint text is displayed when a user setting is disabled due to an organization policy." } } diff --git a/apps/browser/src/_locales/nb/messages.json b/apps/browser/src/_locales/nb/messages.json index 04c76c63d1f..7091c084082 100644 --- a/apps/browser/src/_locales/nb/messages.json +++ b/apps/browser/src/_locales/nb/messages.json @@ -5714,5 +5714,9 @@ }, "confirmKeyConnectorDomain": { "message": "Confirm Key Connector domain" + }, + "settingDisabledByPolicy": { + "message": "This setting is disabled by your organization's policy.", + "description": "This hint text is displayed when a user setting is disabled due to an organization policy." } } diff --git a/apps/browser/src/_locales/ne/messages.json b/apps/browser/src/_locales/ne/messages.json index db205f4740b..026e24dbd3a 100644 --- a/apps/browser/src/_locales/ne/messages.json +++ b/apps/browser/src/_locales/ne/messages.json @@ -5714,5 +5714,9 @@ }, "confirmKeyConnectorDomain": { "message": "Confirm Key Connector domain" + }, + "settingDisabledByPolicy": { + "message": "This setting is disabled by your organization's policy.", + "description": "This hint text is displayed when a user setting is disabled due to an organization policy." } } diff --git a/apps/browser/src/_locales/nl/messages.json b/apps/browser/src/_locales/nl/messages.json index 9d6d54bfe70..7d8760a8710 100644 --- a/apps/browser/src/_locales/nl/messages.json +++ b/apps/browser/src/_locales/nl/messages.json @@ -5714,5 +5714,9 @@ }, "confirmKeyConnectorDomain": { "message": "Key Connector-domein bevestigen" + }, + "settingDisabledByPolicy": { + "message": "Deze instelling is uitgeschakeld door het beleid van uw organisatie.", + "description": "This hint text is displayed when a user setting is disabled due to an organization policy." } } diff --git a/apps/browser/src/_locales/nn/messages.json b/apps/browser/src/_locales/nn/messages.json index db205f4740b..026e24dbd3a 100644 --- a/apps/browser/src/_locales/nn/messages.json +++ b/apps/browser/src/_locales/nn/messages.json @@ -5714,5 +5714,9 @@ }, "confirmKeyConnectorDomain": { "message": "Confirm Key Connector domain" + }, + "settingDisabledByPolicy": { + "message": "This setting is disabled by your organization's policy.", + "description": "This hint text is displayed when a user setting is disabled due to an organization policy." } } diff --git a/apps/browser/src/_locales/or/messages.json b/apps/browser/src/_locales/or/messages.json index db205f4740b..026e24dbd3a 100644 --- a/apps/browser/src/_locales/or/messages.json +++ b/apps/browser/src/_locales/or/messages.json @@ -5714,5 +5714,9 @@ }, "confirmKeyConnectorDomain": { "message": "Confirm Key Connector domain" + }, + "settingDisabledByPolicy": { + "message": "This setting is disabled by your organization's policy.", + "description": "This hint text is displayed when a user setting is disabled due to an organization policy." } } diff --git a/apps/browser/src/_locales/pl/messages.json b/apps/browser/src/_locales/pl/messages.json index 3de2aa27d90..13d00bc6f88 100644 --- a/apps/browser/src/_locales/pl/messages.json +++ b/apps/browser/src/_locales/pl/messages.json @@ -5714,5 +5714,9 @@ }, "confirmKeyConnectorDomain": { "message": "Potwierdź domenę Key Connector" + }, + "settingDisabledByPolicy": { + "message": "This setting is disabled by your organization's policy.", + "description": "This hint text is displayed when a user setting is disabled due to an organization policy." } } diff --git a/apps/browser/src/_locales/pt_BR/messages.json b/apps/browser/src/_locales/pt_BR/messages.json index b9069525242..b1a6bc73f63 100644 --- a/apps/browser/src/_locales/pt_BR/messages.json +++ b/apps/browser/src/_locales/pt_BR/messages.json @@ -5714,5 +5714,9 @@ }, "confirmKeyConnectorDomain": { "message": "Confirmar domínio do Conector de Chave" + }, + "settingDisabledByPolicy": { + "message": "Essa configuração está desativada pela política da sua organização.", + "description": "This hint text is displayed when a user setting is disabled due to an organization policy." } } diff --git a/apps/browser/src/_locales/pt_PT/messages.json b/apps/browser/src/_locales/pt_PT/messages.json index a1d403b54da..54eff3eb2ed 100644 --- a/apps/browser/src/_locales/pt_PT/messages.json +++ b/apps/browser/src/_locales/pt_PT/messages.json @@ -5714,5 +5714,9 @@ }, "confirmKeyConnectorDomain": { "message": "Confirmar o domínio do Key Connector" + }, + "settingDisabledByPolicy": { + "message": "Esta configuração está desativada pela política da sua organização.", + "description": "This hint text is displayed when a user setting is disabled due to an organization policy." } } diff --git a/apps/browser/src/_locales/ro/messages.json b/apps/browser/src/_locales/ro/messages.json index b9a501ce461..9c1e2bcd79a 100644 --- a/apps/browser/src/_locales/ro/messages.json +++ b/apps/browser/src/_locales/ro/messages.json @@ -5714,5 +5714,9 @@ }, "confirmKeyConnectorDomain": { "message": "Confirm Key Connector domain" + }, + "settingDisabledByPolicy": { + "message": "This setting is disabled by your organization's policy.", + "description": "This hint text is displayed when a user setting is disabled due to an organization policy." } } diff --git a/apps/browser/src/_locales/ru/messages.json b/apps/browser/src/_locales/ru/messages.json index 2abae0ffde6..1100c4b382c 100644 --- a/apps/browser/src/_locales/ru/messages.json +++ b/apps/browser/src/_locales/ru/messages.json @@ -5714,5 +5714,9 @@ }, "confirmKeyConnectorDomain": { "message": "Подтвердите домен соединителя ключей" + }, + "settingDisabledByPolicy": { + "message": "Этот параметр отключен политикой вашей организации.", + "description": "This hint text is displayed when a user setting is disabled due to an organization policy." } } diff --git a/apps/browser/src/_locales/si/messages.json b/apps/browser/src/_locales/si/messages.json index 6291fc0a40c..76d4464489b 100644 --- a/apps/browser/src/_locales/si/messages.json +++ b/apps/browser/src/_locales/si/messages.json @@ -5714,5 +5714,9 @@ }, "confirmKeyConnectorDomain": { "message": "Confirm Key Connector domain" + }, + "settingDisabledByPolicy": { + "message": "This setting is disabled by your organization's policy.", + "description": "This hint text is displayed when a user setting is disabled due to an organization policy." } } diff --git a/apps/browser/src/_locales/sk/messages.json b/apps/browser/src/_locales/sk/messages.json index 130d4db6503..0d10ec1dd6b 100644 --- a/apps/browser/src/_locales/sk/messages.json +++ b/apps/browser/src/_locales/sk/messages.json @@ -5714,5 +5714,9 @@ }, "confirmKeyConnectorDomain": { "message": "Potvrdiť doménu Key Connectora" + }, + "settingDisabledByPolicy": { + "message": "Politika organizácie vypla toto nastavenie.", + "description": "This hint text is displayed when a user setting is disabled due to an organization policy." } } diff --git a/apps/browser/src/_locales/sl/messages.json b/apps/browser/src/_locales/sl/messages.json index 253e4fb3550..923fd2ce058 100644 --- a/apps/browser/src/_locales/sl/messages.json +++ b/apps/browser/src/_locales/sl/messages.json @@ -5714,5 +5714,9 @@ }, "confirmKeyConnectorDomain": { "message": "Confirm Key Connector domain" + }, + "settingDisabledByPolicy": { + "message": "This setting is disabled by your organization's policy.", + "description": "This hint text is displayed when a user setting is disabled due to an organization policy." } } diff --git a/apps/browser/src/_locales/sr/messages.json b/apps/browser/src/_locales/sr/messages.json index db482e74175..0cd98548b0f 100644 --- a/apps/browser/src/_locales/sr/messages.json +++ b/apps/browser/src/_locales/sr/messages.json @@ -5714,5 +5714,9 @@ }, "confirmKeyConnectorDomain": { "message": "Потврдите домен конектора кључа" + }, + "settingDisabledByPolicy": { + "message": "This setting is disabled by your organization's policy.", + "description": "This hint text is displayed when a user setting is disabled due to an organization policy." } } diff --git a/apps/browser/src/_locales/sv/messages.json b/apps/browser/src/_locales/sv/messages.json index 979750a4d56..e5de8bb5edf 100644 --- a/apps/browser/src/_locales/sv/messages.json +++ b/apps/browser/src/_locales/sv/messages.json @@ -5714,5 +5714,9 @@ }, "confirmKeyConnectorDomain": { "message": "Bekräfta Key Connector-domän" + }, + "settingDisabledByPolicy": { + "message": "Denna inställning är inaktiverad enligt din organisations policy.", + "description": "This hint text is displayed when a user setting is disabled due to an organization policy." } } diff --git a/apps/browser/src/_locales/ta/messages.json b/apps/browser/src/_locales/ta/messages.json index fea818db9f4..68ae29a7a93 100644 --- a/apps/browser/src/_locales/ta/messages.json +++ b/apps/browser/src/_locales/ta/messages.json @@ -5714,5 +5714,9 @@ }, "confirmKeyConnectorDomain": { "message": "Key Connector டொமைனை உறுதிப்படுத்து" + }, + "settingDisabledByPolicy": { + "message": "This setting is disabled by your organization's policy.", + "description": "This hint text is displayed when a user setting is disabled due to an organization policy." } } diff --git a/apps/browser/src/_locales/te/messages.json b/apps/browser/src/_locales/te/messages.json index db205f4740b..026e24dbd3a 100644 --- a/apps/browser/src/_locales/te/messages.json +++ b/apps/browser/src/_locales/te/messages.json @@ -5714,5 +5714,9 @@ }, "confirmKeyConnectorDomain": { "message": "Confirm Key Connector domain" + }, + "settingDisabledByPolicy": { + "message": "This setting is disabled by your organization's policy.", + "description": "This hint text is displayed when a user setting is disabled due to an organization policy." } } diff --git a/apps/browser/src/_locales/th/messages.json b/apps/browser/src/_locales/th/messages.json index 9a8c074fb0c..cfb23d95a02 100644 --- a/apps/browser/src/_locales/th/messages.json +++ b/apps/browser/src/_locales/th/messages.json @@ -5714,5 +5714,9 @@ }, "confirmKeyConnectorDomain": { "message": "Confirm Key Connector domain" + }, + "settingDisabledByPolicy": { + "message": "This setting is disabled by your organization's policy.", + "description": "This hint text is displayed when a user setting is disabled due to an organization policy." } } diff --git a/apps/browser/src/_locales/tr/messages.json b/apps/browser/src/_locales/tr/messages.json index 3969bb8095c..206c0da5b88 100644 --- a/apps/browser/src/_locales/tr/messages.json +++ b/apps/browser/src/_locales/tr/messages.json @@ -5714,5 +5714,9 @@ }, "confirmKeyConnectorDomain": { "message": "Key Connector alan adını doğrulayın" + }, + "settingDisabledByPolicy": { + "message": "This setting is disabled by your organization's policy.", + "description": "This hint text is displayed when a user setting is disabled due to an organization policy." } } diff --git a/apps/browser/src/_locales/uk/messages.json b/apps/browser/src/_locales/uk/messages.json index b2988e6e9cb..1adbf19496b 100644 --- a/apps/browser/src/_locales/uk/messages.json +++ b/apps/browser/src/_locales/uk/messages.json @@ -5714,5 +5714,9 @@ }, "confirmKeyConnectorDomain": { "message": "Підтвердити домен Key Connector" + }, + "settingDisabledByPolicy": { + "message": "This setting is disabled by your organization's policy.", + "description": "This hint text is displayed when a user setting is disabled due to an organization policy." } } diff --git a/apps/browser/src/_locales/vi/messages.json b/apps/browser/src/_locales/vi/messages.json index c94ba40c9a6..885fe83f667 100644 --- a/apps/browser/src/_locales/vi/messages.json +++ b/apps/browser/src/_locales/vi/messages.json @@ -5714,5 +5714,9 @@ }, "confirmKeyConnectorDomain": { "message": "Xác nhận tên miền Key Connector" + }, + "settingDisabledByPolicy": { + "message": "This setting is disabled by your organization's policy.", + "description": "This hint text is displayed when a user setting is disabled due to an organization policy." } } diff --git a/apps/browser/src/_locales/zh_CN/messages.json b/apps/browser/src/_locales/zh_CN/messages.json index c0c48881394..738e2c13ecb 100644 --- a/apps/browser/src/_locales/zh_CN/messages.json +++ b/apps/browser/src/_locales/zh_CN/messages.json @@ -3226,7 +3226,7 @@ } }, "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { - "message": "仅会导出与 $ORGANIZATION$ 关联的组织密码库,不包括我的项目集合。", + "message": "仅会导出与 $ORGANIZATION$ 关联的组织密码库。不包括「我的项目」集合。", "placeholders": { "organization": { "content": "$1", @@ -5714,5 +5714,9 @@ }, "confirmKeyConnectorDomain": { "message": "确认 Key Connector 域名" + }, + "settingDisabledByPolicy": { + "message": "此设置被您组织的策略禁用了。", + "description": "This hint text is displayed when a user setting is disabled due to an organization policy." } } diff --git a/apps/browser/src/_locales/zh_TW/messages.json b/apps/browser/src/_locales/zh_TW/messages.json index eb3e9d98eb6..b83e78a3b02 100644 --- a/apps/browser/src/_locales/zh_TW/messages.json +++ b/apps/browser/src/_locales/zh_TW/messages.json @@ -6,7 +6,7 @@ "message": "Bitwarden logo" }, "extName": { - "message": "Bitwarden - 免費密碼管理工具", + "message": "Bitwarden - 密碼管理工具", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { @@ -559,7 +559,7 @@ "description": "Verb" }, "unArchive": { - "message": "Unarchive" + "message": "取消封存" }, "itemsInArchive": { "message": "封存中的項目" @@ -571,10 +571,10 @@ "message": "封存的項目會顯示在此處,且不會出現在一般搜尋結果或自動填入建議中。" }, "itemWasSentToArchive": { - "message": "Item was sent to archive" + "message": "項目已移至封存" }, "itemUnarchived": { - "message": "Item was unarchived" + "message": "項目取消封存" }, "archiveItem": { "message": "封存項目" @@ -5580,30 +5580,30 @@ "message": "歡迎來到你的密碼庫!" }, "phishingPageTitleV2": { - "message": "Phishing attempt detected" + "message": "偵測到網路釣魚嘗試" }, "phishingPageSummary": { - "message": "The site you are attempting to visit is a known malicious site and a security risk." + "message": "你正要造訪的網站為已知惡意網站,存在安全風險。" }, "phishingPageCloseTabV2": { - "message": "Close this tab" + "message": "關閉此分頁" }, "phishingPageContinueV2": { - "message": "Continue to this site (not recommended)" + "message": "繼續前往此網站(不建議)" }, "phishingPageExplanation1": { - "message": "This site was found in ", + "message": "此網站被列於", "description": "This is in multiple parts to allow for bold text in the middle of the sentence. A proper name follows this." }, "phishingPageExplanation2": { - "message": ", an open-source list of known phishing sites used for stealing personal and sensitive information.", + "message": ",這是一份開源的已知網路釣魚網站清單,用於竊取個人與敏感資訊。", "description": "This is in multiple parts to allow for bold text in the middle of the sentence. A proper name precedes this." }, "phishingPageLearnMore": { - "message": "Learn more about phishing detection" + "message": "進一步了解網路釣魚偵測" }, "protectedBy": { - "message": "Protected by $PRODUCT$", + "message": "由 $PRODUCT$ 保護", "placeholders": { "product": { "content": "$1", @@ -5714,5 +5714,9 @@ }, "confirmKeyConnectorDomain": { "message": "確認 Key Connector 網域" + }, + "settingDisabledByPolicy": { + "message": "此設定已被你的組織原則停用。", + "description": "This hint text is displayed when a user setting is disabled due to an organization policy." } } diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index dcb1430d7ba..283594b87f5 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -1483,6 +1483,7 @@ export default class MainBackground { await this.sdkLoadService.loadAndInit(); // Only the "true" background should run migrations await this.migrationRunner.run(); + this.encryptService.init(this.configService); // This is here instead of in the InitService b/c we don't plan for // side effects to run in the Browser InitService. diff --git a/apps/browser/src/popup/services/init.service.ts b/apps/browser/src/popup/services/init.service.ts index 1930dbd1d4b..1de1731013d 100644 --- a/apps/browser/src/popup/services/init.service.ts +++ b/apps/browser/src/popup/services/init.service.ts @@ -3,6 +3,8 @@ import { inject, Inject, Injectable } from "@angular/core"; import { AbstractThemingService } from "@bitwarden/angular/platform/services/theming/theming.service.abstraction"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService as LogServiceAbstraction } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -29,6 +31,8 @@ export class InitService { private sdkLoadService: SdkLoadService, private viewCacheService: PopupViewCacheService, private readonly migrationRunner: MigrationRunner, + private configService: ConfigService, + private encryptService: EncryptService, @Inject(DOCUMENT) private document: Document, ) {} @@ -40,6 +44,7 @@ export class InitService { this.twoFactorService.init(); await this.viewCacheService.init(); await this.sizeService.init(); + this.encryptService.init(this.configService); const htmlEl = window.document.documentElement; this.themingService.applyThemeChangesTo(this.document); diff --git a/apps/cli/src/service-container/service-container.ts b/apps/cli/src/service-container/service-container.ts index 3453e0cff70..d10849bae0f 100644 --- a/apps/cli/src/service-container/service-container.ts +++ b/apps/cli/src/service-container/service-container.ts @@ -984,6 +984,7 @@ export class ServiceContainer { this.containerService.attachToGlobal(global); await this.i18nService.init(); this.twoFactorService.init(); + this.encryptService.init(this.configService); // If a user has a BW_SESSION key stored in their env (not process.env.BW_SESSION), // this should set the user key to unlock the vault on init. diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index 6d156dbbcec..0b5cd628119 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -1817,13 +1817,14 @@ version = "0.0.0" dependencies = [ "desktop_core", "futures", - "log", "oslog", "serde", "serde_json", "tokio", "tokio-util", "tracing", + "tracing-oslog", + "tracing-subscriber", "uniffi", ] @@ -3413,6 +3414,18 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-oslog" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d76902d2a8d5f9f55a81155c08971734071968c90f2d9bfe645fe700579b2950" +dependencies = [ + "cc", + "cfg-if", + "tracing-core", + "tracing-subscriber", +] + [[package]] name = "tracing-subscriber" version = "0.3.20" diff --git a/apps/desktop/desktop_native/core/src/ssh_agent/mod.rs b/apps/desktop/desktop_native/core/src/ssh_agent/mod.rs index 3440a0114ae..61cb8fc187d 100644 --- a/apps/desktop/desktop_native/core/src/ssh_agent/mod.rs +++ b/apps/desktop/desktop_native/core/src/ssh_agent/mod.rs @@ -1,6 +1,9 @@ -use std::sync::{ - atomic::{AtomicBool, AtomicU32}, - Arc, +use std::{ + collections::HashMap, + sync::{ + atomic::{AtomicBool, AtomicU32}, + Arc, RwLock, + }, }; use base64::{engine::general_purpose::STANDARD, Engine as _}; @@ -25,8 +28,8 @@ pub mod peerinfo; mod request_parser; #[derive(Clone)] -pub struct BitwardenDesktopAgent { - keystore: ssh_agent::KeyStore, +pub struct BitwardenDesktopAgent { + keystore: ssh_agent::KeyStore, cancellation_token: CancellationToken, show_ui_request_tx: tokio::sync::mpsc::Sender, get_ui_response_rx: Arc>>, @@ -77,9 +80,7 @@ impl SshKey for BitwardenSshKey { } } -impl ssh_agent::Agent - for BitwardenDesktopAgent -{ +impl ssh_agent::Agent for BitwardenDesktopAgent { async fn confirm( &self, ssh_key: BitwardenSshKey, @@ -179,7 +180,23 @@ impl ssh_agent::Agent } } -impl BitwardenDesktopAgent { +impl BitwardenDesktopAgent { + /// Create a new `BitwardenDesktopAgent` from the provided auth channel handles. + pub fn new( + auth_request_tx: tokio::sync::mpsc::Sender, + auth_response_rx: Arc>>, + ) -> Self { + Self { + keystore: ssh_agent::KeyStore(Arc::new(RwLock::new(HashMap::new()))), + cancellation_token: CancellationToken::new(), + show_ui_request_tx: auth_request_tx, + get_ui_response_rx: auth_response_rx, + request_id: Arc::new(AtomicU32::new(0)), + needs_unlock: Arc::new(AtomicBool::new(true)), + is_running: Arc::new(AtomicBool::new(false)), + } + } + pub fn stop(&self) { if !self.is_running() { error!("Tried to stop agent while it is not running"); diff --git a/apps/desktop/desktop_native/core/src/ssh_agent/unix.rs b/apps/desktop/desktop_native/core/src/ssh_agent/unix.rs index 813ebd61cc1..a45c2f6c0bf 100644 --- a/apps/desktop/desktop_native/core/src/ssh_agent/unix.rs +++ b/apps/desktop/desktop_native/core/src/ssh_agent/unix.rs @@ -1,94 +1,53 @@ -use std::{ - collections::HashMap, - fs, - os::unix::fs::PermissionsExt, - sync::{ - atomic::{AtomicBool, AtomicU32}, - Arc, RwLock, - }, -}; +use std::{fs, os::unix::fs::PermissionsExt, path::PathBuf, sync::Arc}; +use anyhow::anyhow; use bitwarden_russh::ssh_agent; use homedir::my_home; use tokio::{net::UnixListener, sync::Mutex}; -use tokio_util::sync::CancellationToken; use tracing::{error, info}; use crate::ssh_agent::peercred_unix_listener_stream::PeercredUnixListenerStream; -use super::{BitwardenDesktopAgent, BitwardenSshKey, SshAgentUIRequest}; +use super::{BitwardenDesktopAgent, SshAgentUIRequest}; -impl BitwardenDesktopAgent { +/// User can override the default socket path with this env var +const ENV_BITWARDEN_SSH_AUTH_SOCK: &str = "BITWARDEN_SSH_AUTH_SOCK"; + +const FLATPAK_DATA_DIR: &str = ".var/app/com.bitwarden.desktop/data"; + +const SOCKFILE_NAME: &str = ".bitwarden-ssh-agent.sock"; + +impl BitwardenDesktopAgent { + /// Starts the Bitwarden Desktop SSH Agent server. + /// # Errors + /// Will return `Err` if unable to create and set permissions for socket file path or + /// if unable to bind to the socket path. pub fn start_server( auth_request_tx: tokio::sync::mpsc::Sender, auth_response_rx: Arc>>, ) -> Result { - let agent = BitwardenDesktopAgent { - keystore: ssh_agent::KeyStore(Arc::new(RwLock::new(HashMap::new()))), - cancellation_token: CancellationToken::new(), - show_ui_request_tx: auth_request_tx, - get_ui_response_rx: auth_response_rx, - request_id: Arc::new(AtomicU32::new(0)), - needs_unlock: Arc::new(AtomicBool::new(true)), - is_running: Arc::new(AtomicBool::new(false)), - }; - let cloned_agent_state = agent.clone(); - tokio::spawn(async move { - let ssh_path = match std::env::var("BITWARDEN_SSH_AUTH_SOCK") { - Ok(path) => path, - Err(_) => { - info!("BITWARDEN_SSH_AUTH_SOCK not set, using default path"); + let agent_state = BitwardenDesktopAgent::new(auth_request_tx, auth_response_rx); - let ssh_agent_directory = match my_home() { - Ok(Some(home)) => home, - _ => { - info!("Could not determine home directory"); - return; - } - }; + let socket_path = get_socket_path()?; - let is_flatpak = std::env::var("container") == Ok("flatpak".to_string()); - if !is_flatpak { - ssh_agent_directory - .join(".bitwarden-ssh-agent.sock") - .to_str() - .expect("Path should be valid") - .to_owned() - } else { - ssh_agent_directory - .join(".var/app/com.bitwarden.desktop/data/.bitwarden-ssh-agent.sock") - .to_str() - .expect("Path should be valid") - .to_owned() - } - } - }; + // if the socket is already present and wasn't cleanly removed during a previous + // runtime, remove it before beginning anew. + remove_path(&socket_path)?; - info!(socket = %ssh_path, "Starting SSH Agent server"); - let sockname = std::path::Path::new(&ssh_path); - if let Err(e) = std::fs::remove_file(sockname) { - error!(error = %e, socket = %ssh_path, "Could not remove existing socket file"); - if e.kind() != std::io::ErrorKind::NotFound { - return; - } - } + info!(?socket_path, "Starting SSH Agent server"); - match UnixListener::bind(sockname) { - Ok(listener) => { - // Only the current user should be able to access the socket - if let Err(e) = fs::set_permissions(sockname, fs::Permissions::from_mode(0o600)) - { - error!(error = %e, socket = ?sockname, "Could not set socket permissions"); - return; - } + match UnixListener::bind(socket_path.clone()) { + Ok(listener) => { + // Only the current user should be able to access the socket + set_user_permissions(&socket_path)?; - let stream = PeercredUnixListenerStream::new(listener); + let stream = PeercredUnixListenerStream::new(listener); - let cloned_keystore = cloned_agent_state.keystore.clone(); - let cloned_cancellation_token = cloned_agent_state.cancellation_token.clone(); - cloned_agent_state - .is_running - .store(true, std::sync::atomic::Ordering::Relaxed); + let cloned_agent_state = agent_state.clone(); + let cloned_keystore = cloned_agent_state.keystore.clone(); + let cloned_cancellation_token = cloned_agent_state.cancellation_token.clone(); + + tokio::spawn(async move { let _ = ssh_agent::serve( stream, cloned_agent_state.clone(), @@ -96,17 +55,132 @@ impl BitwardenDesktopAgent { cloned_cancellation_token, ) .await; + cloned_agent_state .is_running .store(false, std::sync::atomic::Ordering::Relaxed); - info!("SSH Agent server exited"); - } - Err(e) => { - error!(error = %e, socket = %ssh_path, "Unable to start start agent server"); - } - } - }); - Ok(agent) + info!("SSH Agent server exited"); + }); + + agent_state + .is_running + .store(true, std::sync::atomic::Ordering::Relaxed); + + info!(?socket_path, "SSH Agent is running."); + } + Err(error) => { + error!(%error, ?socket_path, "Unable to start start agent server"); + return Err(error.into()); + } + } + + Ok(agent_state) + } +} + +// one of the following: +// - only the env var socket path if it is defined +// - the $HOME path and our well known extension +fn get_socket_path() -> Result { + if let Ok(path) = std::env::var(ENV_BITWARDEN_SSH_AUTH_SOCK) { + Ok(PathBuf::from(path)) + } else { + info!("BITWARDEN_SSH_AUTH_SOCK not set, using default path"); + get_default_socket_path() + } +} + +fn is_flatpak() -> bool { + std::env::var("container") == Ok("flatpak".to_string()) +} + +// use the $HOME directory +fn get_default_socket_path() -> Result { + let Ok(Some(mut ssh_agent_directory)) = my_home() else { + error!("Could not determine home directory"); + return Err(anyhow!("Could not determine home directory.")); + }; + + if is_flatpak() { + ssh_agent_directory = ssh_agent_directory.join(FLATPAK_DATA_DIR); + } + + ssh_agent_directory = ssh_agent_directory.join(SOCKFILE_NAME); + + Ok(ssh_agent_directory) +} + +fn set_user_permissions(path: &PathBuf) -> Result<(), anyhow::Error> { + fs::set_permissions(path, fs::Permissions::from_mode(0o600)) + .map_err(|e| anyhow!("Could not set socket permissions for {path:?}: {e}")) +} + +// try to remove the given path if it exists +fn remove_path(path: &PathBuf) -> Result<(), anyhow::Error> { + if let Ok(true) = std::fs::exists(path) { + std::fs::remove_file(path).map_err(|e| anyhow!("Error removing socket {path:?}: {e}"))?; + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use rand::{distr::Alphanumeric, Rng}; + + use super::*; + + #[test] + fn test_default_socket_path_success() { + let path = get_default_socket_path().unwrap(); + let expected = PathBuf::from_iter([ + std::env::var("HOME").unwrap(), + ".bitwarden-ssh-agent.sock".to_string(), + ]); + assert_eq!(path, expected); + } + + fn rand_file_in_temp() -> PathBuf { + let mut path = std::env::temp_dir(); + let s: String = rand::rng() + .sample_iter(&Alphanumeric) + .take(16) + .map(char::from) + .collect(); + path.push(s); + path + } + + #[test] + fn test_remove_path_exists_success() { + let path = rand_file_in_temp(); + fs::write(&path, "").unwrap(); + remove_path(&path).unwrap(); + + assert!(!fs::exists(&path).unwrap()); + } + + // the remove_path should not fail if the path does not exist + #[test] + fn test_remove_path_not_found_success() { + let path = rand_file_in_temp(); + remove_path(&path).unwrap(); + + assert!(!fs::exists(&path).unwrap()); + } + + #[test] + fn test_sock_path_file_permissions() { + let path = rand_file_in_temp(); + fs::write(&path, "").unwrap(); + + set_user_permissions(&path).unwrap(); + + let metadata = fs::metadata(&path).unwrap(); + let permissions = metadata.permissions().mode(); + + assert_eq!(permissions, 0o100_600); + + remove_path(&path).unwrap(); } } diff --git a/apps/desktop/desktop_native/core/src/ssh_agent/windows.rs b/apps/desktop/desktop_native/core/src/ssh_agent/windows.rs index 75c47165960..662a4658ede 100644 --- a/apps/desktop/desktop_native/core/src/ssh_agent/windows.rs +++ b/apps/desktop/desktop_native/core/src/ssh_agent/windows.rs @@ -1,32 +1,18 @@ use bitwarden_russh::ssh_agent; pub mod named_pipe_listener_stream; -use std::{ - collections::HashMap, - sync::{ - atomic::{AtomicBool, AtomicU32}, - Arc, RwLock, - }, -}; +use std::sync::Arc; use tokio::sync::Mutex; -use tokio_util::sync::CancellationToken; -use super::{BitwardenDesktopAgent, BitwardenSshKey, SshAgentUIRequest}; +use super::{BitwardenDesktopAgent, SshAgentUIRequest}; -impl BitwardenDesktopAgent { +impl BitwardenDesktopAgent { pub fn start_server( auth_request_tx: tokio::sync::mpsc::Sender, auth_response_rx: Arc>>, ) -> Result { - let agent_state = BitwardenDesktopAgent { - keystore: ssh_agent::KeyStore(Arc::new(RwLock::new(HashMap::new()))), - show_ui_request_tx: auth_request_tx, - get_ui_response_rx: auth_response_rx, - cancellation_token: CancellationToken::new(), - request_id: Arc::new(AtomicU32::new(0)), - needs_unlock: Arc::new(AtomicBool::new(true)), - is_running: Arc::new(AtomicBool::new(true)), - }; + let agent_state = BitwardenDesktopAgent::new(auth_request_tx, auth_response_rx); + let stream = named_pipe_listener_stream::NamedPipeServerStream::new( agent_state.cancellation_token.clone(), agent_state.is_running.clone(), diff --git a/apps/desktop/desktop_native/deny.toml b/apps/desktop/desktop_native/deny.toml new file mode 100644 index 00000000000..7d7a126f694 --- /dev/null +++ b/apps/desktop/desktop_native/deny.toml @@ -0,0 +1,40 @@ +# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html +[advisories] +ignore = [ + # Vulnerability in `rsa` crate: https://rustsec.org/advisories/RUSTSEC-2023-0071.html + { id = "RUSTSEC-2023-0071", reason = "There is no fix available yet." }, + { id = "RUSTSEC-2024-0436", reason = "paste crate is unmaintained."} +] + +# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html +[licenses] +# See https://spdx.org/licenses/ for list of possible licenses +allow = [ + "0BSD", + "Apache-2.0", + "BSD-2-Clause", + "BSD-3-Clause", + "BSL-1.0", + "ISC", + "MIT", + "MPL-2.0", + "Unicode-3.0", + "Zlib", +] + + +[licenses.private] +# If true, ignores workspace crates that aren't published, or are only +# published to private registries. +# To see how to mark a crate as unpublished (to the official registry), +# visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field. +ignore = true + +# This section is considered when running `cargo deny check bans`. +# More documentation about the 'bans' section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html +[bans] +deny = [ +# TODO: enable after https://github.com/bitwarden/clients/pull/16761 is merged +# { name = "log", wrappers = [], reason = "Use `tracing` and `tracing-subscriber` for observability needs." }, +] diff --git a/apps/desktop/desktop_native/macos_provider/Cargo.toml b/apps/desktop/desktop_native/macos_provider/Cargo.toml index 9f042209b06..97a8b7d545a 100644 --- a/apps/desktop/desktop_native/macos_provider/Cargo.toml +++ b/apps/desktop/desktop_native/macos_provider/Cargo.toml @@ -16,12 +16,13 @@ bench = false [dependencies] desktop_core = { path = "../core" } futures = { workspace = true } -log = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } tokio = { workspace = true, features = ["sync"] } tokio-util = { workspace = true } tracing = { workspace = true } +tracing-oslog = "0.3.0" +tracing-subscriber = { workspace = true } uniffi = { workspace = true, features = ["cli"] } [target.'cfg(target_os = "macos")'.dependencies] diff --git a/apps/desktop/desktop_native/macos_provider/src/lib.rs b/apps/desktop/desktop_native/macos_provider/src/lib.rs index ded133bcb54..789a56d3048 100644 --- a/apps/desktop/desktop_native/macos_provider/src/lib.rs +++ b/apps/desktop/desktop_native/macos_provider/src/lib.rs @@ -2,13 +2,18 @@ use std::{ collections::HashMap, - sync::{atomic::AtomicU32, Arc, Mutex}, + sync::{atomic::AtomicU32, Arc, Mutex, Once}, time::Instant, }; use futures::FutureExt; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use tracing::{error, info}; +use tracing_subscriber::{ + filter::{EnvFilter, LevelFilter}, + layer::SubscriberExt, + util::SubscriberInitExt, +}; uniffi::setup_scaffolding!(); @@ -21,6 +26,8 @@ use assertion::{ }; use registration::{PasskeyRegistrationRequest, PreparePasskeyRegistrationCallback}; +static INIT: Once = Once::new(); + #[derive(uniffi::Enum, Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub enum UserVerification { @@ -65,9 +72,20 @@ impl MacOSProviderClient { #[allow(clippy::unwrap_used)] #[uniffi::constructor] pub fn connect() -> Self { - let _ = oslog::OsLogger::new("com.bitwarden.desktop.autofill-extension") - .level_filter(log::LevelFilter::Trace) - .init(); + INIT.call_once(|| { + let filter = EnvFilter::builder() + // Everything logs at `INFO` + .with_default_directive(LevelFilter::INFO.into()) + .from_env_lossy(); + + tracing_subscriber::registry() + .with(filter) + .with(tracing_oslog::OsLogger::new( + "com.bitwarden.desktop.autofill-extension", + "default", + )) + .init(); + }); let (from_server_send, mut from_server_recv) = tokio::sync::mpsc::channel(32); let (to_server_send, to_server_recv) = tokio::sync::mpsc::channel(32); diff --git a/apps/desktop/desktop_native/napi/src/lib.rs b/apps/desktop/desktop_native/napi/src/lib.rs index 3e6a5f00ae2..1f725386c86 100644 --- a/apps/desktop/desktop_native/napi/src/lib.rs +++ b/apps/desktop/desktop_native/napi/src/lib.rs @@ -169,7 +169,6 @@ pub mod clipboards { pub mod sshagent { use std::sync::Arc; - use desktop_core::ssh_agent::BitwardenSshKey; use napi::{ bindgen_prelude::Promise, threadsafe_function::{ErrorStrategy::CalleeHandled, ThreadsafeFunction}, @@ -179,7 +178,7 @@ pub mod sshagent { #[napi] pub struct SshAgentState { - state: desktop_core::ssh_agent::BitwardenDesktopAgent, + state: desktop_core::ssh_agent::BitwardenDesktopAgent, } #[napi(object)] diff --git a/apps/desktop/src/app/services/init.service.ts b/apps/desktop/src/app/services/init.service.ts index 6b511ff366d..79c93c1390e 100644 --- a/apps/desktop/src/app/services/init.service.ts +++ b/apps/desktop/src/app/services/init.service.ts @@ -9,6 +9,7 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv import { TwoFactorService as TwoFactorServiceAbstraction } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { DefaultVaultTimeoutService } from "@bitwarden/common/key-management/vault-timeout"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service"; @@ -52,6 +53,7 @@ export class InitService { private autofillService: DesktopAutofillService, private autotypeService: DesktopAutotypeService, private sdkLoadService: SdkLoadService, + private configService: ConfigService, @Inject(DOCUMENT) private document: Document, private readonly migrationRunner: MigrationRunner, ) {} @@ -62,6 +64,7 @@ export class InitService { await this.sshAgentService.init(); this.nativeMessagingService.init(); await this.migrationRunner.waitForCompletion(); // Desktop will run migrations in the main process + this.encryptService.init(this.configService); const accounts = await firstValueFrom(this.accountService.accounts$); const setUserKeyInMemoryPromises = []; diff --git a/apps/desktop/src/locales/az/messages.json b/apps/desktop/src/locales/az/messages.json index 8c1e1bd23d6..3bcd3f5f92b 100644 --- a/apps/desktop/src/locales/az/messages.json +++ b/apps/desktop/src/locales/az/messages.json @@ -2550,7 +2550,7 @@ } }, "vaultCustomTimeoutMinimum": { - "message": "Minimum custom timeout is 1 minute." + "message": "Minimum özəl bitmə vaxtı 1 dəqiqədir." }, "inviteAccepted": { "message": "Dəvət qəbul edildi" diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index 498b8e6e413..d3b3f6cd445 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -4167,7 +4167,7 @@ "itemWasSentToArchive": { "message": "Item was sent to archive" }, - "itemUnarchived": { + "itemWasUnarchived": { "message": "Item was unarchived" }, "archiveItem": { diff --git a/apps/desktop/src/locales/pl/messages.json b/apps/desktop/src/locales/pl/messages.json index 6359bbccb00..4abade3d1c8 100644 --- a/apps/desktop/src/locales/pl/messages.json +++ b/apps/desktop/src/locales/pl/messages.json @@ -2550,7 +2550,7 @@ } }, "vaultCustomTimeoutMinimum": { - "message": "Minimum custom timeout is 1 minute." + "message": "Minimalny niestandardowy czas to 1 minuta." }, "inviteAccepted": { "message": "Zaproszenie zostało zaakceptowane" @@ -2667,7 +2667,7 @@ } }, "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "message": "Tylko sejf organizacji $ORGANIZATION$ zostanie wyeksportowany.", "placeholders": { "organization": { "content": "$1", @@ -2676,7 +2676,7 @@ } }, "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "message": "Tylko sejf organizacji $ORGANIZATION$ zostanie wyeksportowany. Twoje kolekcje nie zostaną uwzględnione.", "placeholders": { "organization": { "content": "$1", @@ -4114,13 +4114,13 @@ "message": "Bitwarden does not validate input locations, be sure you are in the right window and field before using the shortcut." }, "typeShortcut": { - "message": "Type shortcut" + "message": "Rodzaj skrótu" }, "editAutotypeShortcutDescription": { "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." }, "invalidShortcut": { - "message": "Invalid shortcut" + "message": "Skrót jest nieprawidłowy" }, "moreBreadcrumbs": { "message": "Więcej nawigacji", @@ -4153,7 +4153,7 @@ "description": "Verb" }, "unArchive": { - "message": "Unarchive" + "message": "Usuń z archiwum" }, "itemsInArchive": { "message": "Elementy w archiwum" @@ -4165,10 +4165,10 @@ "message": "Zarchiwizowane elementy pojawią się tutaj i zostaną wykluczone z wyników wyszukiwania i sugestii autouzupełniania." }, "itemWasSentToArchive": { - "message": "Item was sent to archive" + "message": "Element został przeniesiony do archiwum" }, "itemUnarchived": { - "message": "Item was unarchived" + "message": "Element został usunięty z archiwum" }, "archiveItem": { "message": "Archiwizuj element" diff --git a/apps/desktop/src/locales/zh_CN/messages.json b/apps/desktop/src/locales/zh_CN/messages.json index 2193e69af24..d3ec23a7994 100644 --- a/apps/desktop/src/locales/zh_CN/messages.json +++ b/apps/desktop/src/locales/zh_CN/messages.json @@ -2676,7 +2676,7 @@ } }, "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { - "message": "仅会导出与 $ORGANIZATION$ 关联的组织密码库,不包括我的项目集合。", + "message": "仅会导出与 $ORGANIZATION$ 关联的组织密码库。不包括「我的项目」集合。", "placeholders": { "organization": { "content": "$1", diff --git a/apps/desktop/src/locales/zh_TW/messages.json b/apps/desktop/src/locales/zh_TW/messages.json index 4a081aadb1a..f22651b960b 100644 --- a/apps/desktop/src/locales/zh_TW/messages.json +++ b/apps/desktop/src/locales/zh_TW/messages.json @@ -2550,7 +2550,7 @@ } }, "vaultCustomTimeoutMinimum": { - "message": "Minimum custom timeout is 1 minute." + "message": "自訂逾時時間最小為 1 分鐘。" }, "inviteAccepted": { "message": "邀請已接受" @@ -4153,7 +4153,7 @@ "description": "Verb" }, "unArchive": { - "message": "Unarchive" + "message": "取消封存" }, "itemsInArchive": { "message": "封存中的項目" @@ -4165,10 +4165,10 @@ "message": "封存的項目會顯示在此處,且不會出現在一般搜尋結果或自動填入建議中。" }, "itemWasSentToArchive": { - "message": "Item was sent to archive" + "message": "項目已移至封存" }, "itemUnarchived": { - "message": "Item was unarchived" + "message": "項目取消封存" }, "archiveItem": { "message": "封存項目" diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts index d5484213a90..fbb83a1bf56 100644 --- a/apps/desktop/src/main.ts +++ b/apps/desktop/src/main.ts @@ -221,7 +221,7 @@ export class Main { ); this.messagingMain = new MessagingMain(this, this.desktopSettingsService); - this.updaterMain = new UpdaterMain(this.i18nService, this.windowMain); + this.updaterMain = new UpdaterMain(this.i18nService, this.logService, this.windowMain); const messageSubject = new Subject>>(); this.messagingService = MessageSender.combine( diff --git a/apps/desktop/src/main/updater.main.ts b/apps/desktop/src/main/updater.main.ts index 51d5073911e..60b4f282405 100644 --- a/apps/desktop/src/main/updater.main.ts +++ b/apps/desktop/src/main/updater.main.ts @@ -1,8 +1,9 @@ -import { dialog, shell } from "electron"; +import { dialog, shell, Notification } from "electron"; import log from "electron-log"; import { autoUpdater, UpdateDownloadedEvent, VerifyUpdateSupport } from "electron-updater"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { LogService } from "@bitwarden/logging"; import { isAppImage, isDev, isMacAppStore, isWindowsPortable, isWindowsStore } from "../utils"; @@ -11,6 +12,8 @@ import { WindowMain } from "./window.main"; const UpdaterCheckInitialDelay = 5 * 1000; // 5 seconds const UpdaterCheckInterval = 12 * 60 * 60 * 1000; // 12 hours +const MaxTimeBeforeBlockingUpdateNotification = 7 * 24 * 60 * 60 * 1000; // 7 days + export class UpdaterMain { private doingUpdateCheck = false; private doingUpdateCheckWithFeedback = false; @@ -18,8 +21,19 @@ export class UpdaterMain { private updateDownloaded: UpdateDownloadedEvent = null; private originalRolloutFunction: VerifyUpdateSupport = null; + // This needs to be tracked to avoid the Notification being garbage collected, + // which would break the click handler. + private openedNotification: Notification | null = null; + + // This is used to set when the initial update notification was shown. + // The system notifications can be easy to miss or be disabled, so we want to + // ensure the user is eventually made aware of the update. If the user does not + // interact with the notification in a reasonable time, we will prompt them again. + private initialUpdateNotificationTime: number | null = null; + constructor( private i18nService: I18nService, + private logService: LogService, private windowMain: WindowMain, ) { autoUpdater.logger = log; @@ -43,6 +57,8 @@ export class UpdaterMain { }); autoUpdater.on("update-available", async () => { + this.initialUpdateNotificationTime ??= Date.now(); + if (this.doingUpdateCheckWithFeedback) { if (this.windowMain.win == null) { this.reset(); @@ -87,7 +103,7 @@ export class UpdaterMain { } this.updateDownloaded = info; - await this.promptRestartUpdate(info); + await this.promptRestartUpdate(info, this.doingUpdateCheckWithFeedback); }); autoUpdater.on("error", (error) => { @@ -108,7 +124,7 @@ export class UpdaterMain { } if (this.updateDownloaded && withFeedback) { - await this.promptRestartUpdate(this.updateDownloaded); + await this.promptRestartUpdate(this.updateDownloaded, true); return; } @@ -144,7 +160,50 @@ export class UpdaterMain { this.updateDownloaded = null; } - private async promptRestartUpdate(info: UpdateDownloadedEvent) { + private async promptRestartUpdate(info: UpdateDownloadedEvent, blocking: boolean) { + // If we have an initial notification, and it's from a long time ago, + // we will block the user with a dialog to ensure they see it. + const longTimeSinceInitialNotification = + this.initialUpdateNotificationTime != null && + Date.now() - this.initialUpdateNotificationTime > MaxTimeBeforeBlockingUpdateNotification; + + if (!longTimeSinceInitialNotification && !blocking && Notification.isSupported()) { + // If the prompt doesn't have to block and we support notifications, + // we will show a notification instead of a blocking dialog, which won't steal focus. + await this.promptRestartUpdateUsingSystemNotification(info); + } else { + // If we are blocking, or notifications are not supported, we will show a blocking dialog. + // This will steal the user's focus, so we should only do this for user initiated actions + // or when there are no other options. + await this.promptRestartUpdateUsingDialog(info); + } + } + + private async promptRestartUpdateUsingSystemNotification(info: UpdateDownloadedEvent) { + if (this.openedNotification != null) { + this.openedNotification.close(); + } + + this.openedNotification = new Notification({ + title: this.i18nService.t("bitwarden") + " - " + this.i18nService.t("restartToUpdate"), + body: this.i18nService.t("restartToUpdateDesc", info.version), + timeoutType: "never", + silent: false, + }); + + // If the user clicks the notification, prompt again to restart, this time with a blocking dialog. + this.openedNotification.on("click", () => { + void this.promptRestartUpdate(info, true); + }); + // If the notification fails to show, fall back to the blocking dialog as well. + this.openedNotification.on("failed", (error) => { + this.logService.error("Update notification failed", error); + void this.promptRestartUpdate(info, true); + }); + this.openedNotification.show(); + } + + private async promptRestartUpdateUsingDialog(info: UpdateDownloadedEvent) { const result = await dialog.showMessageBox(this.windowMain.win, { type: "info", title: this.i18nService.t("bitwarden") + " - " + this.i18nService.t("restartToUpdate"), diff --git a/apps/desktop/src/vault/app/vault/item-footer.component.html b/apps/desktop/src/vault/app/vault/item-footer.component.html index 162335d03bb..859b2f1bdc5 100644 --- a/apps/desktop/src/vault/app/vault/item-footer.component.html +++ b/apps/desktop/src/vault/app/vault/item-footer.component.html @@ -46,7 +46,23 @@ -
+
+ + +
  • + + + +
  • diff --git a/apps/desktop/src/vault/app/vault/vault-items-v2.component.ts b/apps/desktop/src/vault/app/vault/vault-items-v2.component.ts index 06654fb1a5c..290a38ac08c 100644 --- a/apps/desktop/src/vault/app/vault/vault-items-v2.component.ts +++ b/apps/desktop/src/vault/app/vault/vault-items-v2.component.ts @@ -7,6 +7,7 @@ import { distinctUntilChanged, debounceTime } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { VaultItemsComponent as BaseVaultItemsComponent } from "@bitwarden/angular/vault/components/vault-items.component"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { uuidAsString } from "@bitwarden/common/platform/abstractions/sdk/sdk.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { SearchService } from "@bitwarden/common/vault/abstractions/search.service"; @@ -33,8 +34,9 @@ export class VaultItemsV2Component extends BaseVaultIt cipherService: CipherService, accountService: AccountService, restrictedItemTypesService: RestrictedItemTypesService, + configService: ConfigService, ) { - super(searchService, cipherService, accountService, restrictedItemTypesService); + super(searchService, cipherService, accountService, restrictedItemTypesService, configService); this.searchBarService.searchText$ .pipe(debounceTime(SearchTextDebounceInterval), distinctUntilChanged(), takeUntilDestroyed()) diff --git a/apps/desktop/src/vault/app/vault/vault-v2.component.html b/apps/desktop/src/vault/app/vault/vault-v2.component.html index a3f55f0ec63..2696dd0d452 100644 --- a/apps/desktop/src/vault/app/vault/vault-v2.component.html +++ b/apps/desktop/src/vault/app/vault/vault-v2.component.html @@ -19,6 +19,7 @@ (onClone)="cloneCipher($event)" (onDelete)="deleteCipher()" (onCancel)="cancelCipher($event)" + (onArchiveToggle)="refreshCurrentCipher()" [masterPasswordAlreadyPrompted]="cipherRepromptId === cipherId" >
    diff --git a/apps/desktop/src/vault/app/vault/vault-v2.component.ts b/apps/desktop/src/vault/app/vault/vault-v2.component.ts index e6fff50beb7..97d4dc14d0a 100644 --- a/apps/desktop/src/vault/app/vault/vault-v2.component.ts +++ b/apps/desktop/src/vault/app/vault/vault-v2.component.ts @@ -20,6 +20,8 @@ import { AuthRequestServiceAbstraction } from "@bitwarden/auth/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; +import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; @@ -33,6 +35,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { getByIds } from "@bitwarden/common/platform/misc"; import { SyncService } from "@bitwarden/common/platform/sync"; import { CipherId, OrganizationId, UserId } from "@bitwarden/common/types/guid"; +import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service"; @@ -74,6 +77,7 @@ import { DefaultCipherFormConfigService, PasswordRepromptService, CipherFormComponent, + ArchiveCipherUtilitiesService, } from "@bitwarden/vault"; import { NavComponent } from "../../../app/layout/nav.component"; @@ -211,6 +215,9 @@ export class VaultV2Component private folderService: FolderService, private configService: ConfigService, private authRequestService: AuthRequestServiceAbstraction, + private cipherArchiveService: CipherArchiveService, + private policyService: PolicyService, + private archiveCipherUtilitiesService: ArchiveCipherUtilitiesService, ) {} async ngOnInit() { @@ -481,6 +488,12 @@ export class VaultV2Component async viewCipherMenu(c: CipherViewLike) { const cipher = await this.cipherService.getFullCipherView(c); + const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + const userCanArchive = await firstValueFrom(this.cipherArchiveService.userCanArchive$(userId)); + const orgOwnershipPolicy = await firstValueFrom( + this.policyService.policyAppliesToUser$(PolicyType.OrganizationDataOwnership, userId), + ); + const menu: RendererMenuItem[] = [ { label: this.i18nService.t("view"), @@ -505,7 +518,11 @@ export class VaultV2Component }); }, }); - if (!cipher.organizationId) { + + const archivedWithOrgOwnership = cipher.isArchived && orgOwnershipPolicy; + const canCloneArchived = !cipher.isArchived || userCanArchive; + + if (!cipher.organizationId && !archivedWithOrgOwnership && canCloneArchived) { menu.push({ label: this.i18nService.t("clone"), click: () => { @@ -529,6 +546,26 @@ export class VaultV2Component } } + if (userCanArchive && !cipher.organizationId && !cipher.isDeleted && !cipher.isArchived) { + menu.push({ + label: this.i18nService.t("archiveVerb"), + click: async () => { + await this.archiveCipherUtilitiesService.archiveCipher(cipher); + await this.refreshCurrentCipher(); + }, + }); + } + + if (cipher.isArchived) { + menu.push({ + label: this.i18nService.t("unArchive"), + click: async () => { + await this.archiveCipherUtilitiesService.unarchiveCipher(cipher); + await this.refreshCurrentCipher(); + }, + }); + } + switch (cipher.type) { case CipherType.Login: if ( @@ -714,8 +751,6 @@ export class VaultV2Component this.cipherId = cipher.id; this.cipher = cipher; - - await this.vaultItemsComponent?.load(this.activeFilter.buildFilter()).catch(() => {}); await this.go().catch(() => {}); await this.vaultItemsComponent?.refresh().catch(() => {}); } @@ -748,7 +783,11 @@ export class VaultV2Component ); this.activeFilter = vaultFilter; await this.vaultItemsComponent - ?.reload(this.activeFilter.buildFilter(), vaultFilter.status === "trash") + ?.reload( + this.activeFilter.buildFilter(), + vaultFilter.status === "trash", + vaultFilter.status === "archive", + ) .catch(() => {}); await this.go().catch(() => {}); } @@ -822,6 +861,20 @@ export class VaultV2Component } } + /** Refresh the current cipher object */ + protected async refreshCurrentCipher() { + if (!this.cipher) { + return; + } + + this.cipher = await firstValueFrom( + this.cipherService.cipherViews$(this.activeUserId!).pipe( + filter((c) => !!c), + map((ciphers) => ciphers.find((c) => c.id === this.cipherId) ?? null), + ), + ); + } + private dirtyInput(): boolean { return ( (this.action === "add" || this.action === "edit" || this.action === "clone") && diff --git a/apps/web/src/app/core/init.service.ts b/apps/web/src/app/core/init.service.ts index a3358ff7253..6b03913ef7a 100644 --- a/apps/web/src/app/core/init.service.ts +++ b/apps/web/src/app/core/init.service.ts @@ -9,6 +9,7 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv import { TwoFactorService as TwoFactorServiceAbstraction } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { DefaultVaultTimeoutService } from "@bitwarden/common/key-management/vault-timeout"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service"; import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service"; import { IpcService } from "@bitwarden/common/platform/ipc"; @@ -40,6 +41,7 @@ export class InitService { private ipcService: IpcService, private sdkLoadService: SdkLoadService, private taskService: TaskService, + private configService: ConfigService, private readonly migrationRunner: MigrationRunner, @Inject(DOCUMENT) private document: Document, ) {} @@ -48,6 +50,7 @@ export class InitService { return async () => { await this.sdkLoadService.loadAndInit(); await this.migrationRunner.run(); + this.encryptService.init(this.configService); const activeAccount = await firstValueFrom(this.accountService.activeAccount$); if (activeAccount) { diff --git a/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.html b/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.html index a897c8c4c2c..43ce8530d55 100644 --- a/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.html +++ b/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.html @@ -93,7 +93,7 @@ - @if (!decryptionFailure && !hideMenu) { + @if (!decryptionFailure) { - - - - @@ -130,6 +130,53 @@ + + + + + + + + + + + + + + + + + + + +