diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index cf656967da0..12a8c182335 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -4,10 +4,6 @@ # # https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners -## Secrets Manager team files ## -bitwarden_license/bit-web/src/app/secrets-manager @bitwarden/team-secrets-manager-dev -apps/web/src/app/secrets-manager/ @bitwarden/team-secrets-manager-dev - ## Auth team files ## apps/browser/src/auth @bitwarden/team-auth-dev apps/cli/src/auth @bitwarden/team-auth-dev diff --git a/.github/workflows/build-desktop.yml b/.github/workflows/build-desktop.yml index dc15f841c2b..bc9bdec396a 100644 --- a/.github/workflows/build-desktop.yml +++ b/.github/workflows/build-desktop.yml @@ -170,7 +170,7 @@ jobs: - name: Set up environment run: | sudo apt-get update - sudo apt-get -y install pkg-config libxss-dev libsecret-1-dev rpm musl-dev musl-tools flatpak flatpak-builder + sudo apt-get -y install pkg-config libxss-dev rpm musl-dev musl-tools flatpak flatpak-builder - name: Set up Snap run: sudo snap install snapcraft --classic diff --git a/.github/workflows/release-desktop-beta.yml b/.github/workflows/release-desktop-beta.yml index c1646997201..a940ce289ff 100644 --- a/.github/workflows/release-desktop-beta.yml +++ b/.github/workflows/release-desktop-beta.yml @@ -138,7 +138,7 @@ jobs: - name: Set up environment run: | sudo apt-get update - sudo apt-get -y install pkg-config libxss-dev libsecret-1-dev rpm + sudo apt-get -y install pkg-config libxss-dev rpm - name: Set up Snap run: sudo snap install snapcraft --classic diff --git a/.storybook/main.ts b/.storybook/main.ts index 454da4377dc..b48a86ba2b2 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -57,6 +57,7 @@ const config: StorybookConfig = { return config; }, docs: {}, + staticDirs: ["../apps/web/src/images"], }; export default config; diff --git a/apps/browser/src/auth/popup/settings/account-security.component.ts b/apps/browser/src/auth/popup/settings/account-security.component.ts index 8493358aa8b..1939b3f5716 100644 --- a/apps/browser/src/auth/popup/settings/account-security.component.ts +++ b/apps/browser/src/auth/popup/settings/account-security.component.ts @@ -438,8 +438,8 @@ export class AccountSecurityComponent implements OnInit, OnDestroy { const successful = await this.trySetupBiometrics(); this.form.controls.biometric.setValue(successful); + await this.biometricStateService.setBiometricUnlockEnabled(successful); if (!successful) { - await this.biometricStateService.setBiometricUnlockEnabled(false); await this.biometricStateService.setFingerprintValidated(false); } } else { diff --git a/apps/browser/src/auth/popup/update-temp-password.component.html b/apps/browser/src/auth/popup/update-temp-password.component.html index 6e0cc0f4483..0ce82aa20cf 100644 --- a/apps/browser/src/auth/popup/update-temp-password.component.html +++ b/apps/browser/src/auth/popup/update-temp-password.component.html @@ -1,7 +1,7 @@
- {{ "logOut" | i18n }} +

{{ "updateMasterPassword" | i18n }} diff --git a/apps/browser/src/autofill/background/overlay.background.ts b/apps/browser/src/autofill/background/overlay.background.ts index c42d1f7e640..7b80c436798 100644 --- a/apps/browser/src/autofill/background/overlay.background.ts +++ b/apps/browser/src/autofill/background/overlay.background.ts @@ -362,8 +362,6 @@ export class OverlayBackground implements OverlayBackgroundInterface { this.inlineMenuFido2Credentials.clear(); this.storeInlineMenuFido2Credentials$.next(currentTab.id); - await this.generatePassword(); - const ciphersViews = await this.getCipherViews(currentTab, updateAllCipherTypes); for (let cipherIndex = 0; cipherIndex < ciphersViews.length; cipherIndex++) { this.inlineMenuCiphers.set(`inline-menu-cipher-${cipherIndex}`, ciphersViews[cipherIndex]); diff --git a/apps/browser/src/tools/popup/services/file-popout-utils.service.ts b/apps/browser/src/tools/popup/services/file-popout-utils.service.ts index 65a311e47c3..fa72e411316 100644 --- a/apps/browser/src/tools/popup/services/file-popout-utils.service.ts +++ b/apps/browser/src/tools/popup/services/file-popout-utils.service.ts @@ -49,7 +49,7 @@ export class FilePopoutUtilsService { } /** - * Determines whether to show a file popout callout message for Chromium-based browsers in Linux and Mac OS X Big Sur + * Determines whether to show a file popout callout message for Chromium-based browsers in Linux and Mac OS X * @param win - The window context in which the check should be performed. * @returns True if the extension is not in a sidebar or popout; otherwise, false. */ @@ -66,8 +66,6 @@ export class FilePopoutUtilsService { } private isUnsupportedMac(win: Window): boolean { - return ( - this.platformUtilsService.isChrome() && win?.navigator?.appVersion.includes("Mac OS X 11") - ); + return this.platformUtilsService.isChrome() && win?.navigator?.appVersion.includes("Mac OS X"); } } diff --git a/apps/browser/src/vault/popup/components/vault/vault-filter.component.html b/apps/browser/src/vault/popup/components/vault/vault-filter.component.html index f5c28b2bebd..bf557f74608 100644 --- a/apps/browser/src/vault/popup/components/vault/vault-filter.component.html +++ b/apps/browser/src/vault/popup/components/vault/vault-filter.component.html @@ -118,6 +118,7 @@ type="button" class="box-content-row" appStopClick + *ngIf="isSshKeysEnabled" (click)="selectType(cipherType.SshKey)" >
diff --git a/apps/browser/src/vault/popup/components/vault/vault-filter.component.ts b/apps/browser/src/vault/popup/components/vault/vault-filter.component.ts index 448f85a8cbd..d12b2fd801d 100644 --- a/apps/browser/src/vault/popup/components/vault/vault-filter.component.ts +++ b/apps/browser/src/vault/popup/components/vault/vault-filter.component.ts @@ -7,7 +7,9 @@ import { first, switchMap, takeUntil } from "rxjs/operators"; import { CollectionView } from "@bitwarden/admin-console/common"; import { VaultFilter } from "@bitwarden/angular/vault/vault-filter/models/vault-filter.model"; import { SearchService } from "@bitwarden/common/abstractions/search.service"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { SyncService } from "@bitwarden/common/platform/sync"; @@ -62,6 +64,8 @@ export class VaultFilterComponent implements OnInit, OnDestroy { selectedOrganization: string = null; showCollections = true; + isSshKeysEnabled = false; + private loadedTimeout: number; private selectedTimeout: number; private preventSelected = false; @@ -95,6 +99,7 @@ export class VaultFilterComponent implements OnInit, OnDestroy { private location: Location, private vaultFilterService: VaultFilterService, private vaultBrowserStateService: VaultBrowserStateService, + private configService: ConfigService, ) { this.noFolderListSize = 100; } @@ -166,6 +171,8 @@ export class VaultFilterComponent implements OnInit, OnDestroy { .subscribe((isSearchable) => { this.isSearchable = isSearchable; }); + + this.isSshKeysEnabled = await this.configService.getFeatureFlag(FeatureFlag.SSHKeyVaultItem); } ngOnDestroy() { diff --git a/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.html b/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.html index 69322b08c8a..a79b6c74b03 100644 --- a/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.html +++ b/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.html @@ -30,7 +30,7 @@ - diff --git a/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.ts b/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.ts index de9d95aab00..6827823704e 100644 --- a/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.ts +++ b/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.ts @@ -1,5 +1,5 @@ import { CommonModule } from "@angular/common"; -import { Component, Input } from "@angular/core"; +import { ChangeDetectionStrategy, Component, Input } from "@angular/core"; import { Router } from "@angular/router"; import { JslibModule } from "@bitwarden/angular/jslib.module"; @@ -17,7 +17,7 @@ import { ToastService, TypographyModule, } from "@bitwarden/components"; -import { PasswordRepromptService } from "@bitwarden/vault"; +import { CanDeleteCipherDirective, PasswordRepromptService } from "@bitwarden/vault"; @Component({ selector: "app-trash-list-items-container", @@ -29,10 +29,12 @@ import { PasswordRepromptService } from "@bitwarden/vault"; JslibModule, SectionComponent, SectionHeaderComponent, + CanDeleteCipherDirective, MenuModule, IconButtonModule, TypographyModule, ], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class TrashListItemsContainerComponent { /** diff --git a/apps/browser/src/vault/popup/settings/trash.component.ts b/apps/browser/src/vault/popup/settings/trash.component.ts index b6f77ef6a52..8bac22df53f 100644 --- a/apps/browser/src/vault/popup/settings/trash.component.ts +++ b/apps/browser/src/vault/popup/settings/trash.component.ts @@ -1,5 +1,5 @@ import { CommonModule } from "@angular/common"; -import { Component } from "@angular/core"; +import { ChangeDetectionStrategy, Component } from "@angular/core"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { CalloutModule, NoItemsModule } from "@bitwarden/components"; @@ -27,6 +27,7 @@ import { TrashListItemsContainerComponent } from "./trash-list-items-container/t CalloutModule, NoItemsModule, ], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class TrashComponent { protected deletedCiphers$ = this.vaultPopupItemsService.deletedCiphers$; diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index 16a5a48cd06..82dbebb12df 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -36,6 +36,7 @@ dependencies = [ "cfg-if", "cipher", "cpufeatures", + "zeroize", ] [[package]] @@ -83,6 +84,19 @@ dependencies = [ "x11rb", ] +[[package]] +name = "argon2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" +dependencies = [ + "base64ct", + "blake2", + "cpufeatures", + "password-hash", + "zeroize", +] + [[package]] name = "async-broadcast" version = "0.7.1" @@ -161,6 +175,17 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "async-net" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7" +dependencies = [ + "async-io", + "blocking", + "futures-lite", +] + [[package]] name = "async-process" version = "2.3.0" @@ -320,6 +345,15 @@ dependencies = [ "tokio-util", ] +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -400,16 +434,6 @@ dependencies = [ "shlex", ] -[[package]] -name = "cfg-expr" -version = "0.15.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" -dependencies = [ - "smallvec", - "target-lexicon", -] - [[package]] name = "cfg-if" version = "1.0.0" @@ -447,6 +471,7 @@ checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ "crypto-common", "inout", + "zeroize", ] [[package]] @@ -530,6 +555,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", + "rand_core", "typenum", ] @@ -660,6 +686,7 @@ dependencies = [ "aes", "anyhow", "arboard", + "argon2", "async-stream", "base64", "bitwarden-russh", @@ -669,13 +696,12 @@ dependencies = [ "dirs", "ed25519", "futures", - "gio", "homedir", "interprocess", "keytar", "libc", - "libsecret", "log", + "oo7", "pin-project", "pkcs8", "rand", @@ -1056,105 +1082,12 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" -[[package]] -name = "gio" -version = "0.19.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be548be810e45dd31d3bbb89c6210980bb7af9bca3ea1292b5f16b75f8e394a7" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-util", - "gio-sys", - "glib", - "libc", - "pin-project-lite", - "smallvec", - "thiserror", -] - -[[package]] -name = "gio-sys" -version = "0.19.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cd743ba4714d671ad6b6234e8ab2a13b42304d0e13ab7eba1dcdd78a7d6d4ef" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", - "windows-sys 0.52.0", -] - -[[package]] -name = "glib" -version = "0.19.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39650279f135469465018daae0ba53357942a5212137515777d5fdca74984a44" -dependencies = [ - "bitflags", - "futures-channel", - "futures-core", - "futures-executor", - "futures-task", - "futures-util", - "gio-sys", - "glib-macros", - "glib-sys", - "gobject-sys", - "libc", - "memchr", - "smallvec", - "thiserror", -] - -[[package]] -name = "glib-macros" -version = "0.19.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4429b0277a14ae9751350ad9b658b1be0abb5b54faa5bcdf6e74a3372582fad7" -dependencies = [ - "heck", - "proc-macro-crate", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "glib-sys" -version = "0.19.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c2dc18d3a82b0006d470b13304fbbb3e0a9bd4884cf985a60a7ed733ac2c4a5" -dependencies = [ - "libc", - "system-deps", -] - -[[package]] -name = "gobject-sys" -version = "0.19.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e697e252d6e0416fd1d9e169bda51c0f1c926026c39ca21fbe8b1bb5c3b8b9e" -dependencies = [ - "glib-sys", - "libc", - "system-deps", -] - [[package]] name = "hashbrown" version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - [[package]] name = "hermit-abi" version = "0.3.9" @@ -1173,6 +1106,15 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + [[package]] name = "hmac" version = "0.12.1" @@ -1283,9 +1225,9 @@ dependencies = [ [[package]] name = "libm" -version = "0.2.8" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" [[package]] name = "libredox" @@ -1297,32 +1239,6 @@ dependencies = [ "libc", ] -[[package]] -name = "libsecret" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50c6ccddc706a38eca477b4d7857acd6c76c7d6fba5d47b4b2e7d800e5a17194" -dependencies = [ - "gio", - "glib", - "libc", - "libsecret-sys", -] - -[[package]] -name = "libsecret-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a1af48e61f1c8e77e9705296f346e45b637754a92348a79b4c62df84d0654c2" -dependencies = [ - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "pkg-config", - "system-deps", -] - [[package]] name = "link-cplusplus" version = "1.0.9" @@ -1354,6 +1270,16 @@ version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + [[package]] name = "memchr" version = "2.7.4" @@ -1418,9 +1344,9 @@ checksum = "e1c0f5d67ee408a4685b61f5ab7e58605c8ae3f2b4189f0127d804ff13d5560a" [[package]] name = "napi-derive" -version = "2.16.12" +version = "2.16.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17435f7a00bfdab20b0c27d9c56f58f6499e418252253081bfff448099da31d1" +checksum = "7cbe2585d8ac223f7d34f13701434b9d5f4eb9c332cccce8dee57ea18ab8ab0c" dependencies = [ "cfg-if", "convert_case", @@ -1432,9 +1358,9 @@ dependencies = [ [[package]] name = "napi-derive-backend" -version = "1.0.74" +version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "967c485e00f0bf3b1bdbe510a38a4606919cf1d34d9a37ad41f25a81aa077abe" +checksum = "1639aaa9eeb76e91c6ae66da8ce3e89e921cd3885e99ec85f4abacae72fc91bf" dependencies = [ "convert_case", "once_cell", @@ -1489,6 +1415,30 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + [[package]] name = "num-bigint-dig" version = "0.8.4" @@ -1502,10 +1452,20 @@ dependencies = [ "num-iter", "num-traits", "rand", + "serde", "smallvec", "zeroize", ] +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + [[package]] name = "num-conv" version = "0.1.0" @@ -1532,6 +1492,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -1665,6 +1636,39 @@ version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +[[package]] +name = "oo7" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc6ce4692fbfd044ce22ca07dcab1a30fa12432ca2aa5b1294eca50d3332a24" +dependencies = [ + "aes", + "async-fs", + "async-io", + "async-lock", + "async-net", + "blocking", + "cbc", + "cipher", + "digest", + "endi", + "futures-lite", + "futures-util", + "hkdf", + "hmac", + "md-5", + "num", + "num-bigint-dig", + "pbkdf2", + "rand", + "serde", + "sha2", + "subtle", + "zbus", + "zeroize", + "zvariant", +] + [[package]] name = "opaque-debug" version = "0.3.1" @@ -1726,6 +1730,17 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core", + "subtle", +] + [[package]] name = "pbkdf2" version = "0.12.2" @@ -2182,15 +2197,6 @@ dependencies = [ "syn", ] -[[package]] -name = "serde_spanned" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" -dependencies = [ - "serde", -] - [[package]] name = "sha1" version = "0.10.6" @@ -2360,25 +2366,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "system-deps" -version = "6.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" -dependencies = [ - "cfg-expr", - "heck", - "pkg-config", - "toml", - "version-compare", -] - -[[package]] -name = "target-lexicon" -version = "0.12.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" - [[package]] name = "tempfile" version = "3.13.0" @@ -2505,26 +2492,11 @@ dependencies = [ "tokio", ] -[[package]] -name = "toml" -version = "0.8.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit", -] - [[package]] name = "toml_datetime" version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" -dependencies = [ - "serde", -] [[package]] name = "toml_edit" @@ -2533,8 +2505,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ "indexmap", - "serde", - "serde_spanned", "toml_datetime", "winnow", ] @@ -2628,12 +2598,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "version-compare" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" - [[package]] name = "version_check" version = "0.9.5" @@ -3198,6 +3162,20 @@ name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "zvariant" diff --git a/apps/desktop/desktop_native/core/Cargo.toml b/apps/desktop/desktop_native/core/Cargo.toml index 6abf7fbb0a7..6e17cde2fa7 100644 --- a/apps/desktop/desktop_native/core/Cargo.toml +++ b/apps/desktop/desktop_native/core/Cargo.toml @@ -10,15 +10,13 @@ default = ["sys"] manual_test = [] sys = [ - "dep:widestring", - "dep:windows", - "dep:core-foundation", - "dep:security-framework", - "dep:security-framework-sys", - "dep:gio", - "dep:libsecret", - "dep:zbus", - "dep:zbus_polkit", + "dep:widestring", + "dep:windows", + "dep:core-foundation", + "dep:security-framework", + "dep:security-framework-sys", + "dep:zbus", + "dep:zbus_polkit", ] [dependencies] @@ -27,6 +25,7 @@ anyhow = "=1.0.93" arboard = { version = "=3.4.1", default-features = false, features = [ "wayland-data-control", ] } +argon2 = { version = "=0.5.3", features = ["zeroize"] } async-stream = "=0.3.6" base64 = "=0.22.1" byteorder = "=1.5.0" @@ -84,7 +83,7 @@ security-framework = { version = "=3.0.0", optional = true } security-framework-sys = { version = "=2.12.0", optional = true } [target.'cfg(target_os = "linux")'.dependencies] -gio = { version = "=0.19.5", optional = true } -libsecret = { version = "=0.5.0", optional = true } +oo7 = "=0.3.3" + zbus = { version = "=4.4.0", optional = true } zbus_polkit = { version = "=4.0.0", optional = true } diff --git a/apps/desktop/desktop_native/core/src/biometric/macos.rs b/apps/desktop/desktop_native/core/src/biometric/macos.rs index 01ee4519ce6..ec09d566e1f 100644 --- a/apps/desktop/desktop_native/core/src/biometric/macos.rs +++ b/apps/desktop/desktop_native/core/src/biometric/macos.rs @@ -18,7 +18,7 @@ impl super::BiometricTrait for Biometric { bail!("platform not supported"); } - fn get_biometric_secret( + async fn get_biometric_secret( _service: &str, _account: &str, _key_material: Option, @@ -26,7 +26,7 @@ impl super::BiometricTrait for Biometric { bail!("platform not supported"); } - fn set_biometric_secret( + async fn set_biometric_secret( _service: &str, _account: &str, _secret: &str, diff --git a/apps/desktop/desktop_native/core/src/biometric/mod.rs b/apps/desktop/desktop_native/core/src/biometric/mod.rs index 72352cf2288..dd480f817b6 100644 --- a/apps/desktop/desktop_native/core/src/biometric/mod.rs +++ b/apps/desktop/desktop_native/core/src/biometric/mod.rs @@ -22,20 +22,19 @@ pub struct OsDerivedKey { pub iv_b64: String, } +#[allow(async_fn_in_trait)] pub trait BiometricTrait { - #[allow(async_fn_in_trait)] async fn prompt(hwnd: Vec, message: String) -> Result; - #[allow(async_fn_in_trait)] async fn available() -> Result; fn derive_key_material(secret: Option<&str>) -> Result; - fn set_biometric_secret( + async fn set_biometric_secret( service: &str, account: &str, secret: &str, key_material: Option, iv_b64: &str, ) -> Result; - fn get_biometric_secret( + async fn get_biometric_secret( service: &str, account: &str, key_material: Option, diff --git a/apps/desktop/desktop_native/core/src/biometric/unix.rs b/apps/desktop/desktop_native/core/src/biometric/unix.rs index 563bd1dfe52..5153fc5ed87 100644 --- a/apps/desktop/desktop_native/core/src/biometric/unix.rs +++ b/apps/desktop/desktop_native/core/src/biometric/unix.rs @@ -73,7 +73,7 @@ impl super::BiometricTrait for Biometric { Ok(OsDerivedKey { key_b64, iv_b64 }) } - fn set_biometric_secret( + async fn set_biometric_secret( service: &str, account: &str, secret: &str, @@ -85,11 +85,11 @@ impl super::BiometricTrait for Biometric { ))?; let encrypted_secret = encrypt(secret, &key_material, iv_b64)?; - crate::password::set_password(service, account, &encrypted_secret)?; + crate::password::set_password(service, account, &encrypted_secret).await?; Ok(encrypted_secret) } - fn get_biometric_secret( + async fn get_biometric_secret( service: &str, account: &str, key_material: Option, @@ -98,7 +98,7 @@ impl super::BiometricTrait for Biometric { "Key material is required for polkit protected keys" ))?; - let encrypted_secret = crate::password::get_password(service, account)?; + let encrypted_secret = crate::password::get_password(service, account).await?; let secret = CipherString::from_str(&encrypted_secret)?; return Ok(decrypt(&secret, &key_material)?); } diff --git a/apps/desktop/desktop_native/core/src/biometric/windows.rs b/apps/desktop/desktop_native/core/src/biometric/windows.rs index c951e42e260..fcc5b95cc4a 100644 --- a/apps/desktop/desktop_native/core/src/biometric/windows.rs +++ b/apps/desktop/desktop_native/core/src/biometric/windows.rs @@ -121,7 +121,7 @@ impl super::BiometricTrait for Biometric { Ok(OsDerivedKey { key_b64, iv_b64 }) } - fn set_biometric_secret( + async fn set_biometric_secret( service: &str, account: &str, secret: &str, @@ -133,11 +133,11 @@ impl super::BiometricTrait for Biometric { ))?; let encrypted_secret = encrypt(secret, &key_material, iv_b64)?; - crate::password::set_password(service, account, &encrypted_secret)?; + crate::password::set_password(service, account, &encrypted_secret).await?; Ok(encrypted_secret) } - fn get_biometric_secret( + async fn get_biometric_secret( service: &str, account: &str, key_material: Option, @@ -146,7 +146,7 @@ impl super::BiometricTrait for Biometric { "Key material is required for Windows Hello protected keys" ))?; - let encrypted_secret = crate::password::get_password(service, account)?; + let encrypted_secret = crate::password::get_password(service, account).await?; match CipherString::from_str(&encrypted_secret) { Ok(secret) => { // If the secret is a CipherString, it is encrypted and we need to decrypt it. @@ -292,9 +292,9 @@ mod tests { assert_eq!(decrypt(&secret, &key_material).unwrap(), "secret") } - #[test] - fn get_biometric_secret_requires_key() { - let result = ::get_biometric_secret("", "", None); + #[tokio::test] + async fn get_biometric_secret_requires_key() { + let result = ::get_biometric_secret("", "", None).await; assert!(result.is_err()); assert_eq!( result.unwrap_err().to_string(), @@ -302,29 +302,25 @@ mod tests { ); } - #[test] - fn get_biometric_secret_handles_unencrypted_secret() { - scopeguard::defer! { - crate::password::delete_password("test", "test").unwrap(); - } + #[tokio::test] + async fn get_biometric_secret_handles_unencrypted_secret() { let test = "test"; let secret = "password"; let key_material = KeyMaterial { os_key_part_b64: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=".to_owned(), client_key_part_b64: Some("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=".to_owned()), }; - crate::password::set_password(test, test, secret).unwrap(); + crate::password::set_password(test, test, secret).await.unwrap(); let result = ::get_biometric_secret(test, test, Some(key_material)) + .await .unwrap(); + crate::password::delete_password("test", "test").await.unwrap(); assert_eq!(result, secret); } - #[test] - fn get_biometric_secret_handles_encrypted_secret() { - scopeguard::defer! { - crate::password::delete_password("test", "test").unwrap(); - } + #[tokio::test] + async fn get_biometric_secret_handles_encrypted_secret() { let test = "test"; let secret = CipherString::from_str("0.l9fhDUP/wDJcKwmEzcb/3w==|uP4LcqoCCj5FxBDP77NV6Q==").unwrap(); // output from test_encrypt @@ -332,17 +328,19 @@ mod tests { os_key_part_b64: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=".to_owned(), client_key_part_b64: Some("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=".to_owned()), }; - crate::password::set_password(test, test, &secret.to_string()).unwrap(); + crate::password::set_password(test, test, &secret.to_string()).await.unwrap(); let result = ::get_biometric_secret(test, test, Some(key_material)) + .await .unwrap(); + crate::password::delete_password("test", "test").await.unwrap(); assert_eq!(result, "secret"); } - #[test] - fn set_biometric_secret_requires_key() { - let result = ::set_biometric_secret("", "", "", None, ""); + #[tokio::test] + async fn set_biometric_secret_requires_key() { + let result = ::set_biometric_secret("", "", "", None, "").await; assert!(result.is_err()); assert_eq!( result.unwrap_err().to_string(), diff --git a/apps/desktop/desktop_native/core/src/crypto/crypto.rs b/apps/desktop/desktop_native/core/src/crypto/crypto.rs index b1fd4426fa6..4427138cb1d 100644 --- a/apps/desktop/desktop_native/core/src/crypto/crypto.rs +++ b/apps/desktop/desktop_native/core/src/crypto/crypto.rs @@ -5,7 +5,7 @@ use aes::cipher::{ BlockEncryptMut, KeyIvInit, }; -use crate::error::{CryptoError, Result}; +use crate::error::{CryptoError, KdfParamError, Result}; use super::CipherString; @@ -37,3 +37,53 @@ pub fn encrypt_aes256( Ok(CipherString::AesCbc256_B64 { iv, data }) } + +pub fn argon2( + secret: &[u8], + salt: &[u8], + iterations: u32, + memory: u32, + parallelism: u32, +) -> Result<[u8; 32]> { + use argon2::*; + + let params = Params::new(memory, iterations, parallelism, Some(32)).map_err(|e| { + KdfParamError::InvalidParams(format!("Argon2 parameters are invalid: {e}",)) + })?; + let argon = Argon2::new(Algorithm::Argon2id, Version::V0x13, params); + + let mut hash = [0u8; 32]; + argon + .hash_password_into(secret, &salt, &mut hash) + .map_err(|e| KdfParamError::InvalidParams(format!("Argon2 hashing failed: {e}",)))?; + + // Argon2 is using some stack memory that is not zeroed. Eventually some function will + // overwrite the stack, but we use this trick to force the used stack to be zeroed. + #[inline(never)] + fn clear_stack() { + std::hint::black_box([0u8; 4096]); + } + clear_stack(); + Ok(hash) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_argon2() { + let test_hash: [u8; 32] = [ + 112, 200, 85, 209, 100, 4, 246, 146, 117, 180, 152, 44, 103, 198, 75, 14, 166, 77, 201, + 22, 62, 178, 87, 224, 95, 209, 253, 68, 166, 209, 47, 218, + ]; + let secret = b"supersecurepassword"; + let salt = b"mail@example.com"; + let iterations = 3; + let memory = 1024 * 64; + let parallelism = 4; + + let hash = argon2(secret, salt, iterations, memory, parallelism).unwrap(); + assert_eq!(hash, test_hash,); + } +} diff --git a/apps/desktop/desktop_native/core/src/error.rs b/apps/desktop/desktop_native/core/src/error.rs index d3104cb6f44..34624ed630e 100644 --- a/apps/desktop/desktop_native/core/src/error.rs +++ b/apps/desktop/desktop_native/core/src/error.rs @@ -9,6 +9,8 @@ pub enum Error { #[error("Cryptography Error, {0}")] Crypto(#[from] CryptoError), + #[error("KDF Parameter Error, {0}")] + KdfParam(#[from] KdfParamError), } #[derive(Debug, Error)] @@ -29,6 +31,12 @@ pub enum CryptoError { KeyDecrypt, } +#[derive(Debug, Error)] +pub enum KdfParamError { + #[error("Invalid KDF parameters: {0}")] + InvalidParams(String), +} + // Ensure that the error messages implement Send and Sync #[cfg(test)] const _: () = { diff --git a/apps/desktop/desktop_native/core/src/password/macos.rs b/apps/desktop/desktop_native/core/src/password/macos.rs index 408706423e2..c911a0d2430 100644 --- a/apps/desktop/desktop_native/core/src/password/macos.rs +++ b/apps/desktop/desktop_native/core/src/password/macos.rs @@ -3,26 +3,22 @@ use security_framework::passwords::{ delete_generic_password, get_generic_password, set_generic_password, }; -pub fn get_password(service: &str, account: &str) -> Result { +pub async fn get_password(service: &str, account: &str) -> Result { let result = String::from_utf8(get_generic_password(&service, &account)?)?; Ok(result) } -pub fn get_password_keytar(service: &str, account: &str) -> Result { - get_password(service, account) -} - -pub fn set_password(service: &str, account: &str, password: &str) -> Result<()> { +pub async fn set_password(service: &str, account: &str, password: &str) -> Result<()> { let result = set_generic_password(&service, &account, password.as_bytes())?; Ok(result) } -pub fn delete_password(service: &str, account: &str) -> Result<()> { +pub async fn delete_password(service: &str, account: &str) -> Result<()> { let result = delete_generic_password(&service, &account)?; Ok(result) } -pub fn is_available() -> Result { +pub async fn is_available() -> Result { Ok(true) } @@ -30,18 +26,17 @@ pub fn is_available() -> Result { mod tests { use super::*; - #[test] - fn test() { - scopeguard::defer!(delete_password("BitwardenTest", "BitwardenTest").unwrap_or({});); - set_password("BitwardenTest", "BitwardenTest", "Random").unwrap(); + #[tokio::test] + async fn test() { + set_password("BitwardenTest", "BitwardenTest", "Random").await.unwrap(); assert_eq!( "Random", - get_password("BitwardenTest", "BitwardenTest").unwrap() + get_password("BitwardenTest", "BitwardenTest").await.unwrap() ); - delete_password("BitwardenTest", "BitwardenTest").unwrap(); + delete_password("BitwardenTest", "BitwardenTest").await.unwrap(); // Ensure password is deleted - match get_password("BitwardenTest", "BitwardenTest") { + match get_password("BitwardenTest", "BitwardenTest").await { Ok(_) => panic!("Got a result"), Err(e) => assert_eq!( "The specified item could not be found in the keychain.", @@ -50,9 +45,9 @@ mod tests { } } - #[test] - fn test_error_no_password() { - match get_password("Unknown", "Unknown") { + #[tokio::test] + async fn test_error_no_password() { + match get_password("Unknown", "Unknown").await { Ok(_) => panic!("Got a result"), Err(e) => assert_eq!( "The specified item could not be found in the keychain.", diff --git a/apps/desktop/desktop_native/core/src/password/unix.rs b/apps/desktop/desktop_native/core/src/password/unix.rs index 1817a4d62ee..20a79625efb 100644 --- a/apps/desktop/desktop_native/core/src/password/unix.rs +++ b/apps/desktop/desktop_native/core/src/password/unix.rs @@ -1,106 +1,106 @@ use anyhow::{anyhow, Result}; -use libsecret::{password_clear_sync, password_lookup_sync, password_store_sync, Schema}; +use oo7::dbus::{self}; use std::collections::HashMap; -pub fn get_password(service: &str, account: &str) -> Result { - let res = password_lookup_sync( - Some(&get_schema()), - build_attributes(service, account), - gio::Cancellable::NONE, - )?; - - match res { - Some(s) => Ok(String::from(s)), - None => Err(anyhow!("No password found")), - } -} - -pub fn get_password_keytar(service: &str, account: &str) -> Result { - get_password(service, account) -} - -pub fn set_password(service: &str, account: &str, password: &str) -> Result<()> { - let result = password_store_sync( - Some(&get_schema()), - build_attributes(service, account), - Some(&libsecret::COLLECTION_DEFAULT), - &format!("{}/{}", service, account), - password, - gio::Cancellable::NONE, - )?; - Ok(result) -} - -pub fn delete_password(service: &str, account: &str) -> Result<()> { - let result = password_clear_sync( - Some(&get_schema()), - build_attributes(service, account), - gio::Cancellable::NONE, - )?; - Ok(result) -} - -pub fn is_available() -> Result { - let result = password_clear_sync( - Some(&get_schema()), - build_attributes("bitwardenSecretsAvailabilityTest", "test"), - gio::Cancellable::NONE, - ); - match result { - Ok(_) => Ok(true), +pub async fn get_password(service: &str, account: &str) -> Result { + match get_password_new(service, account).await { + Ok(res) => Ok(res), Err(_) => { - println!("secret-service unavailable: {:?}", result); - Ok(false) + get_password_legacy(service, account).await } } } -fn get_schema() -> Schema { - let mut attributes = std::collections::HashMap::new(); - attributes.insert("service", libsecret::SchemaAttributeType::String); - attributes.insert("account", libsecret::SchemaAttributeType::String); - - libsecret::Schema::new( - "org.freedesktop.Secret.Generic", - libsecret::SchemaFlags::NONE, - attributes, - ) +async fn get_password_new(service: &str, account: &str) -> Result { + let keyring = oo7::Keyring::new().await?; + let attributes = HashMap::from([("service", service), ("account", account)]); + let results = keyring.search_items(&attributes).await?; + let res = results.get(0); + match res { + Some(res) => { + let secret = res.secret().await?; + Ok(String::from_utf8(secret.to_vec())?) + }, + None => Err(anyhow!("no result")) + } } -fn build_attributes<'a>(service: &'a str, account: &'a str) -> HashMap<&'a str, &'a str> { - let mut attributes = HashMap::new(); - attributes.insert("service", service); - attributes.insert("account", account); +// forces to read via secret service; remvove after 2025.03 +async fn get_password_legacy(service: &str, account: &str) -> Result { + println!("falling back to get legacy {} {}", service, account); + let svc = dbus::Service::new().await?; + let collection = svc.default_collection().await?; + let keyring = oo7::Keyring::DBus(collection); + let attributes = HashMap::from([("service", service), ("account", account)]); + let results = keyring.search_items(&attributes).await?; + let res = results.get(0); + match res { + Some(res) => { + let secret = res.secret().await?; + println!("deleting legacy secret service entry {} {}", service, account); + keyring.delete(&attributes).await?; + let secret_string = String::from_utf8(secret.to_vec())?; + set_password(service, account, &secret_string).await?; + Ok(secret_string) + }, + None => Err(anyhow!("no result")) + } +} - attributes +pub async fn set_password(service: &str, account: &str, password: &str) -> Result<()> { + let keyring = oo7::Keyring::new().await?; + let attributes = HashMap::from([("service", service), ("account", account)]); + keyring.create_item("org.freedesktop.Secret.Generic", &attributes, password, true).await?; + Ok(()) +} + +pub async fn delete_password(service: &str, account: &str) -> Result<()> { + let keyring = oo7::Keyring::new().await?; + let attributes = HashMap::from([("service", service), ("account", account)]); + keyring.delete(&attributes).await?; + Ok(()) +} + +pub async fn is_available() -> Result { + match oo7::Keyring::new().await { + Ok(_) => Ok(true), + _ => Ok(false), + } } #[cfg(test)] mod tests { use super::*; - #[test] - fn test() { - scopeguard::defer!(delete_password("BitwardenTest", "BitwardenTest").unwrap_or({});); - set_password("BitwardenTest", "BitwardenTest", "Random").unwrap(); + #[tokio::test] + async fn test() { + set_password("BitwardenTest", "BitwardenTest", "Random").await.unwrap(); assert_eq!( "Random", - get_password("BitwardenTest", "BitwardenTest").unwrap() + get_password("BitwardenTest", "BitwardenTest").await.unwrap() ); - delete_password("BitwardenTest", "BitwardenTest").unwrap(); + delete_password("BitwardenTest", "BitwardenTest").await.unwrap(); // Ensure password is deleted - match get_password("BitwardenTest", "BitwardenTest") { - Ok(_) => panic!("Got a result"), - Err(e) => assert_eq!("No password found", e.to_string()), + match get_password("BitwardenTest", "BitwardenTest").await { + Ok(_) => { + panic!("Got a result") + } + Err(e) => assert_eq!( + "no result", + e.to_string() + ), } } - #[test] - fn test_error_no_password() { - match get_password("BitwardenTest", "BitwardenTest") { + #[tokio::test] + async fn test_error_no_password() { + match get_password("Unknown", "Unknown").await { Ok(_) => panic!("Got a result"), - Err(e) => assert_eq!("No password found", e.to_string()), + Err(e) => assert_eq!( + "no result", + e.to_string() + ), } } } diff --git a/apps/desktop/desktop_native/core/src/password/windows.rs b/apps/desktop/desktop_native/core/src/password/windows.rs index d932aabae95..873e717ac8b 100644 --- a/apps/desktop/desktop_native/core/src/password/windows.rs +++ b/apps/desktop/desktop_native/core/src/password/windows.rs @@ -13,7 +13,7 @@ use windows::{ const CRED_FLAGS_NONE: u32 = 0; -pub fn get_password<'a>(service: &str, account: &str) -> Result { +pub async fn get_password<'a>(service: &str, account: &str) -> Result { let target_name = U16CString::from_str(target_name(service, account))?; let mut credential: *mut CREDENTIALW = std::ptr::null_mut(); @@ -45,39 +45,7 @@ pub fn get_password<'a>(service: &str, account: &str) -> Result { Ok(String::from(password)) } -// Remove this after sufficient releases -pub fn get_password_keytar<'a>(service: &str, account: &str) -> Result { - let target_name = U16CString::from_str(target_name(service, account))?; - - let mut credential: *mut CREDENTIALW = std::ptr::null_mut(); - let credential_ptr = &mut credential; - - let result = unsafe { - CredReadW( - PCWSTR(target_name.as_ptr()), - CRED_TYPE_GENERIC, - CRED_FLAGS_NONE, - credential_ptr, - ) - }; - - scopeguard::defer!({ - unsafe { CredFree(credential as *mut _) }; - }); - - result?; - - let password = unsafe { - std::str::from_utf8_unchecked(std::slice::from_raw_parts( - (*credential).CredentialBlob, - (*credential).CredentialBlobSize as usize, - )) - }; - - Ok(String::from(password)) -} - -pub fn set_password(service: &str, account: &str, password: &str) -> Result<()> { +pub async fn set_password(service: &str, account: &str, password: &str) -> Result<()> { let mut target_name = U16CString::from_str(target_name(service, account))?; let mut user_name = U16CString::from_str(account)?; let last_written = FILETIME { @@ -108,7 +76,7 @@ pub fn set_password(service: &str, account: &str, password: &str) -> Result<()> Ok(()) } -pub fn delete_password(service: &str, account: &str) -> Result<()> { +pub async fn delete_password(service: &str, account: &str) -> Result<()> { let target_name = U16CString::from_str(target_name(service, account))?; unsafe { @@ -122,7 +90,7 @@ pub fn delete_password(service: &str, account: &str) -> Result<()> { Ok(()) } -pub fn is_available() -> Result { +pub async fn is_available() -> Result { Ok(true) } @@ -142,36 +110,25 @@ fn convert_error(e: windows::core::Error) -> String { mod tests { use super::*; - #[test] - fn test() { - scopeguard::defer!(delete_password("BitwardenTest", "BitwardenTest").unwrap_or({});); - set_password("BitwardenTest", "BitwardenTest", "Random").unwrap(); + #[tokio::test] + async fn test() { + set_password("BitwardenTest", "BitwardenTest", "Random").await.unwrap(); assert_eq!( "Random", - get_password("BitwardenTest", "BitwardenTest").unwrap() + get_password("BitwardenTest", "BitwardenTest").await.unwrap() ); - delete_password("BitwardenTest", "BitwardenTest").unwrap(); + delete_password("BitwardenTest", "BitwardenTest").await.unwrap(); // Ensure password is deleted - match get_password("BitwardenTest", "BitwardenTest") { + match get_password("BitwardenTest", "BitwardenTest").await { Ok(_) => panic!("Got a result"), Err(e) => assert_eq!("Password not found.", e.to_string()), } } - #[test] - fn test_get_password_keytar() { - scopeguard::defer!(delete_password("BitwardenTest", "BitwardenTest").unwrap_or({});); - keytar::set_password("BitwardenTest", "BitwardenTest", "HelloFromKeytar").unwrap(); - assert_eq!( - "HelloFromKeytar", - get_password_keytar("BitwardenTest", "BitwardenTest").unwrap() - ); - } - - #[test] - fn test_error_no_password() { - match get_password("BitwardenTest", "BitwardenTest") { + #[tokio::test] + async fn test_error_no_password() { + match get_password("BitwardenTest", "BitwardenTest").await { Ok(_) => panic!("Got a result"), Err(e) => assert_eq!("Password not found.", e.to_string()), } diff --git a/apps/desktop/desktop_native/napi/Cargo.toml b/apps/desktop/desktop_native/napi/Cargo.toml index 644ff8a51d8..6efd662b351 100644 --- a/apps/desktop/desktop_native/napi/Cargo.toml +++ b/apps/desktop/desktop_native/napi/Cargo.toml @@ -19,7 +19,7 @@ hex = "=0.4.3" anyhow = "=1.0.93" desktop_core = { path = "../core" } napi = { version = "=2.16.13", features = ["async"] } -napi-derive = "=2.16.12" +napi-derive = "=2.16.13" tokio = { version = "=1.41.1" } tokio-util = "=0.7.12" tokio-stream = "=0.1.15" diff --git a/apps/desktop/desktop_native/napi/index.d.ts b/apps/desktop/desktop_native/napi/index.d.ts index 956b2d726da..9ceb30c4ff5 100644 --- a/apps/desktop/desktop_native/napi/index.d.ts +++ b/apps/desktop/desktop_native/napi/index.d.ts @@ -6,8 +6,6 @@ export declare namespace passwords { /** Fetch the stored password from the keychain. */ export function getPassword(service: string, account: string): Promise - /** Fetch the stored password from the keychain that was stored with Keytar. */ - export function getPasswordKeytar(service: string, account: string): Promise /** Save the password to the keychain. Adds an entry if none exists otherwise updates the existing entry. */ export function setPassword(service: string, account: string, password: string): Promise /** Delete the stored password from the keychain. */ @@ -124,3 +122,6 @@ export declare namespace ipc { send(message: string): number } } +export declare namespace crypto { + export function argon2(secret: Buffer, salt: Buffer, iterations: number, memory: number, parallelism: number): Promise +} diff --git a/apps/desktop/desktop_native/napi/src/lib.rs b/apps/desktop/desktop_native/napi/src/lib.rs index 4c7bc8eaa93..f160b19ad53 100644 --- a/apps/desktop/desktop_native/napi/src/lib.rs +++ b/apps/desktop/desktop_native/napi/src/lib.rs @@ -8,14 +8,7 @@ pub mod passwords { /// Fetch the stored password from the keychain. #[napi] pub async fn get_password(service: String, account: String) -> napi::Result { - desktop_core::password::get_password(&service, &account) - .map_err(|e| napi::Error::from_reason(e.to_string())) - } - - /// Fetch the stored password from the keychain that was stored with Keytar. - #[napi] - pub async fn get_password_keytar(service: String, account: String) -> napi::Result { - desktop_core::password::get_password_keytar(&service, &account) + desktop_core::password::get_password(&service, &account).await .map_err(|e| napi::Error::from_reason(e.to_string())) } @@ -26,21 +19,21 @@ pub mod passwords { account: String, password: String, ) -> napi::Result<()> { - desktop_core::password::set_password(&service, &account, &password) + desktop_core::password::set_password(&service, &account, &password).await .map_err(|e| napi::Error::from_reason(e.to_string())) } /// Delete the stored password from the keychain. #[napi] pub async fn delete_password(service: String, account: String) -> napi::Result<()> { - desktop_core::password::delete_password(&service, &account) + desktop_core::password::delete_password(&service, &account).await .map_err(|e| napi::Error::from_reason(e.to_string())) } // Checks if the os secure storage is available #[napi] pub async fn is_available() -> napi::Result { - desktop_core::password::is_available().map_err(|e| napi::Error::from_reason(e.to_string())) + desktop_core::password::is_available().await.map_err(|e| napi::Error::from_reason(e.to_string())) } } @@ -81,6 +74,7 @@ pub mod biometrics { key_material.map(|m| m.into()), &iv_b64, ) + .await .map_err(|e| napi::Error::from_reason(e.to_string())) } @@ -92,6 +86,7 @@ pub mod biometrics { ) -> napi::Result { let result = Biometric::get_biometric_secret(&service, &account, key_material.map(|m| m.into())) + .await .map_err(|e| napi::Error::from_reason(e.to_string())); result } @@ -528,3 +523,22 @@ pub mod ipc { } } } + +#[napi] +pub mod crypto { + use napi::bindgen_prelude::Buffer; + + #[napi] + pub async fn argon2( + secret: Buffer, + salt: Buffer, + iterations: u32, + memory: u32, + parallelism: u32, + ) -> napi::Result { + desktop_core::crypto::argon2(&secret, &salt, iterations, memory, parallelism) + .map_err(|e| napi::Error::from_reason(e.to_string())) + .map(|v| v.to_vec()) + .map(|v| Buffer::from(v)) + } +} diff --git a/apps/desktop/electron-builder.json b/apps/desktop/electron-builder.json index 9a8bc45ae20..9b894b0bfc7 100644 --- a/apps/desktop/electron-builder.json +++ b/apps/desktop/electron-builder.json @@ -18,12 +18,7 @@ "**/*", "!**/node_modules/@bitwarden/desktop-napi/**/*", "**/node_modules/@bitwarden/desktop-napi/index.js", - "**/node_modules/@bitwarden/desktop-napi/desktop_napi.${platform}-${arch}*.node", - - "!**/node_modules/argon2/**/*", - "**/node_modules/argon2/argon2.cjs", - "**/node_modules/argon2/package.json", - "**/node_modules/argon2/build/Release/argon2.node" + "**/node_modules/@bitwarden/desktop-napi/desktop_napi.${platform}-${arch}*.node" ], "electronVersion": "32.1.1", "generateUpdatesFilesForAllChannels": true, @@ -229,7 +224,7 @@ }, "deb": { "artifactName": "${productName}-${version}-${arch}.${ext}", - "depends": ["libnotify4", "libxtst6", "libnss3", "libsecret-1-0", "libxss1"] + "depends": ["libnotify4", "libxtst6", "libnss3", "libxss1"] }, "appImage": { "artifactName": "${productName}-${version}-${arch}.${ext}" diff --git a/apps/desktop/src/app/services/renderer-crypto-function.service.ts b/apps/desktop/src/app/services/renderer-crypto-function.service.ts index 604e70b1ebd..ee80dc25933 100644 --- a/apps/desktop/src/app/services/renderer-crypto-function.service.ts +++ b/apps/desktop/src/app/services/renderer-crypto-function.service.ts @@ -19,6 +19,13 @@ export class RendererCryptoFunctionService memory: number, parallelism: number, ): Promise { + if (typeof password === "string") { + password = new TextEncoder().encode(password); + } + if (typeof salt === "string") { + salt = new TextEncoder().encode(salt); + } + return await ipc.platform.crypto.argon2(password, salt, iterations, memory, parallelism); } } diff --git a/apps/desktop/src/package-lock.json b/apps/desktop/src/package-lock.json index 085181e34b2..0300b0b93cc 100644 --- a/apps/desktop/src/package-lock.json +++ b/apps/desktop/src/package-lock.json @@ -9,8 +9,7 @@ "version": "2024.12.0", "license": "GPL-3.0", "dependencies": { - "@bitwarden/desktop-napi": "file:../desktop_native/napi", - "argon2": "0.41.1" + "@bitwarden/desktop-napi": "file:../desktop_native/napi" } }, "../desktop_native/napi": { diff --git a/apps/desktop/src/package.json b/apps/desktop/src/package.json index 0f82609b767..9a3c56cf17c 100644 --- a/apps/desktop/src/package.json +++ b/apps/desktop/src/package.json @@ -12,7 +12,6 @@ "url": "git+https://github.com/bitwarden/clients.git" }, "dependencies": { - "@bitwarden/desktop-napi": "file:../desktop_native/napi", - "argon2": "0.41.1" + "@bitwarden/desktop-napi": "file:../desktop_native/napi" } } diff --git a/apps/desktop/src/platform/main/main-crypto-function.service.ts b/apps/desktop/src/platform/main/main-crypto-function.service.ts index 848e33113e5..2fc3fde1db2 100644 --- a/apps/desktop/src/platform/main/main-crypto-function.service.ts +++ b/apps/desktop/src/platform/main/main-crypto-function.service.ts @@ -1,6 +1,7 @@ import { ipcMain } from "electron"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; +import { crypto } from "@bitwarden/desktop-napi"; import { NodeCryptoFunctionService } from "@bitwarden/node/services/node-crypto-function.service"; export class MainCryptoFunctionService @@ -13,16 +14,16 @@ export class MainCryptoFunctionService async ( event, opts: { - password: string | Uint8Array; - salt: string | Uint8Array; + password: Uint8Array; + salt: Uint8Array; iterations: number; memory: number; parallelism: number; }, ) => { - return await this.argon2( - opts.password, - opts.salt, + return await crypto.argon2( + Buffer.from(opts.password), + Buffer.from(opts.salt), opts.iterations, opts.memory, opts.parallelism, diff --git a/apps/desktop/src/platform/preload.ts b/apps/desktop/src/platform/preload.ts index 519ed684320..30e248d352c 100644 --- a/apps/desktop/src/platform/preload.ts +++ b/apps/desktop/src/platform/preload.ts @@ -99,8 +99,8 @@ const nativeMessaging = { const crypto = { argon2: ( - password: string | Uint8Array, - salt: string | Uint8Array, + password: Uint8Array, + salt: Uint8Array, iterations: number, memory: number, parallelism: number, diff --git a/apps/desktop/src/platform/services/ssh-agent.service.ts b/apps/desktop/src/platform/services/ssh-agent.service.ts index 969300fcc5f..a3e6fb09040 100644 --- a/apps/desktop/src/platform/services/ssh-agent.service.ts +++ b/apps/desktop/src/platform/services/ssh-agent.service.ts @@ -198,7 +198,10 @@ export class SshAgentService implements OnDestroy { } const sshCiphers = ciphers.filter( - (cipher) => cipher.type === CipherType.SshKey && !cipher.isDeleted, + (cipher) => + cipher.type === CipherType.SshKey && + !cipher.isDeleted && + cipher.organizationId === null, ); const keys = sshCiphers.map((cipher) => { return { diff --git a/apps/desktop/src/vault/app/vault/vault-filter/filters/type-filter.component.html b/apps/desktop/src/vault/app/vault/vault-filter/filters/type-filter.component.html index c3dcd191dfc..55e10980ad1 100644 --- a/apps/desktop/src/vault/app/vault/vault-filter/filters/type-filter.component.html +++ b/apps/desktop/src/vault/app/vault/vault-filter/filters/type-filter.component.html @@ -82,6 +82,7 @@
  • {{ "sdksDesc" | i18n }}

    - + diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.spec.ts b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.spec.ts index 6c8ea28bc2f..e4a65f7ddd8 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.spec.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.spec.ts @@ -4,14 +4,17 @@ import { By } from "@angular/platform-browser"; import { mock } from "jest-mock-extended"; import { of } from "rxjs"; +import { SharedModule } from "@bitwarden/components/src/shared"; +import { + IntegrationCardComponent, + IntegrationGridComponent, +} from "@bitwarden/web-vault/app/shared"; + import { SYSTEM_THEME_OBSERVABLE } from "../../../../../../libs/angular/src/services/injection-tokens"; import { I18nService } from "../../../../../../libs/common/src/platform/abstractions/i18n.service"; import { ThemeType } from "../../../../../../libs/common/src/platform/enums"; import { ThemeStateService } from "../../../../../../libs/common/src/platform/theming/theme-state.service"; -import { I18nPipe } from "../../../../../../libs/components/src/shared/i18n.pipe"; -import { IntegrationCardComponent } from "./integration-card/integration-card.component"; -import { IntegrationGridComponent } from "./integration-grid/integration-grid.component"; import { IntegrationsComponent } from "./integrations.component"; @Component({ @@ -31,18 +34,12 @@ describe("IntegrationsComponent", () => { beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ - IntegrationsComponent, - IntegrationGridComponent, - IntegrationCardComponent, - MockHeaderComponent, - MockNewMenuComponent, - I18nPipe, - ], + declarations: [IntegrationsComponent, MockHeaderComponent, MockNewMenuComponent], + imports: [IntegrationGridComponent, IntegrationCardComponent, SharedModule], providers: [ { provide: I18nService, - useValue: mock({ t: (key) => key }), + useValue: mock(), }, { provide: ThemeStateService, diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.ts index 9e846d45034..b8f9386d715 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.ts @@ -1,9 +1,7 @@ import { Component } from "@angular/core"; import { IntegrationType } from "@bitwarden/common/enums"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; - -import { Integration } from "./models/integration"; +import { Integration } from "@bitwarden/web-vault/app/shared"; @Component({ selector: "sm-integrations", @@ -12,11 +10,10 @@ import { Integration } from "./models/integration"; export class IntegrationsComponent { private integrationsAndSdks: Integration[] = []; - constructor(i18nService: I18nService) { + constructor() { this.integrationsAndSdks = [ { name: "Rust", - linkText: i18nService.t("rustSDKRepo"), linkURL: "https://github.com/bitwarden/sdk", image: "../../../../../../../images/secrets-manager/sdks/rust.svg", imageDarkMode: "../../../../../../../images/secrets-manager/sdks/rust-white.svg", @@ -24,7 +21,6 @@ export class IntegrationsComponent { }, { name: "GitHub Actions", - linkText: i18nService.t("setUpGithubActions"), linkURL: "https://bitwarden.com/help/github-actions-integration/", image: "../../../../../../../images/secrets-manager/integrations/github.svg", imageDarkMode: "../../../../../../../images/secrets-manager/integrations/github-white.svg", @@ -32,7 +28,6 @@ export class IntegrationsComponent { }, { name: "GitLab CI/CD", - linkText: i18nService.t("setUpGitlabCICD"), linkURL: "https://bitwarden.com/help/gitlab-integration/", image: "../../../../../../../images/secrets-manager/integrations/gitlab.svg", imageDarkMode: "../../../../../../../images/secrets-manager/integrations/gitlab-white.svg", @@ -40,35 +35,30 @@ export class IntegrationsComponent { }, { name: "Ansible", - linkText: i18nService.t("setUpAnsible"), linkURL: "https://bitwarden.com/help/ansible-integration/", image: "../../../../../../../images/secrets-manager/integrations/ansible.svg", type: IntegrationType.Integration, }, { name: "C#", - linkText: i18nService.t("cSharpSDKRepo"), linkURL: "https://github.com/bitwarden/sdk/tree/main/languages/csharp", image: "../../../../../../../images/secrets-manager/sdks/c-sharp.svg", type: IntegrationType.SDK, }, { name: "C++", - linkText: i18nService.t("cPlusPlusSDKRepo"), linkURL: "https://github.com/bitwarden/sdk/tree/main/languages/cpp", image: "../../../../../../../images/secrets-manager/sdks/c-plus-plus.png", type: IntegrationType.SDK, }, { name: "Go", - linkText: i18nService.t("goSDKRepo"), linkURL: "https://github.com/bitwarden/sdk/tree/main/languages/go", image: "../../../../../../../images/secrets-manager/sdks/go.svg", type: IntegrationType.SDK, }, { name: "Java", - linkText: i18nService.t("javaSDKRepo"), linkURL: "https://github.com/bitwarden/sdk/tree/main/languages/java", image: "../../../../../../../images/secrets-manager/sdks/java.svg", imageDarkMode: "../../../../../../../images/secrets-manager/sdks/java-white.svg", @@ -76,35 +66,30 @@ export class IntegrationsComponent { }, { name: "JS WebAssembly", - linkText: i18nService.t("jsWebAssemblySDKRepo"), linkURL: "https://github.com/bitwarden/sdk/tree/main/languages/js", image: "../../../../../../../images/secrets-manager/sdks/wasm.svg", type: IntegrationType.SDK, }, { name: "php", - linkText: i18nService.t("phpSDKRepo"), linkURL: "https://github.com/bitwarden/sdk/tree/main/languages/php", image: "../../../../../../../images/secrets-manager/sdks/php.svg", type: IntegrationType.SDK, }, { name: "Python", - linkText: i18nService.t("pythonSDKRepo"), linkURL: "https://github.com/bitwarden/sdk/tree/main/languages/python", image: "../../../../../../../images/secrets-manager/sdks/python.svg", type: IntegrationType.SDK, }, { name: "Ruby", - linkText: i18nService.t("rubySDKRepo"), linkURL: "https://github.com/bitwarden/sdk/tree/main/languages/ruby", image: "../../../../../../../images/secrets-manager/sdks/ruby.png", type: IntegrationType.SDK, }, { name: "Kubernetes Operator", - linkText: i18nService.t("setUpKubernetes"), linkURL: "https://bitwarden.com/help/secrets-manager-kubernetes-operator/", image: "../../../../../../../images/secrets-manager/integrations/kubernetes.svg", type: IntegrationType.Integration, diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.module.ts b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.module.ts index 0d26b626f16..b79892f5ed6 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.module.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.module.ts @@ -1,15 +1,22 @@ import { NgModule } from "@angular/core"; +import { + IntegrationCardComponent, + IntegrationGridComponent, +} from "@bitwarden/web-vault/app/shared"; + import { SecretsManagerSharedModule } from "../shared/sm-shared.module"; -import { IntegrationCardComponent } from "./integration-card/integration-card.component"; -import { IntegrationGridComponent } from "./integration-grid/integration-grid.component"; import { IntegrationsRoutingModule } from "./integrations-routing.module"; import { IntegrationsComponent } from "./integrations.component"; @NgModule({ - imports: [SecretsManagerSharedModule, IntegrationsRoutingModule], - declarations: [IntegrationsComponent, IntegrationGridComponent, IntegrationCardComponent], - providers: [], + imports: [ + SecretsManagerSharedModule, + IntegrationsRoutingModule, + IntegrationCardComponent, + IntegrationGridComponent, + ], + declarations: [IntegrationsComponent], }) export class IntegrationsModule {} diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/access-intelligence-routing.module.ts b/bitwarden_license/bit-web/src/app/tools/access-intelligence/access-intelligence-routing.module.ts index c13cc0efae8..993d9c0a134 100644 --- a/bitwarden_license/bit-web/src/app/tools/access-intelligence/access-intelligence-routing.module.ts +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/access-intelligence-routing.module.ts @@ -1,15 +1,14 @@ import { NgModule } from "@angular/core"; import { RouterModule, Routes } from "@angular/router"; -import { canAccessFeature } from "@bitwarden/angular/platform/guard/feature-flag.guard"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { organizationPermissionsGuard } from "@bitwarden/web-vault/app/admin-console/organizations/guards/org-permissions.guard"; import { RiskInsightsComponent } from "./risk-insights.component"; const routes: Routes = [ { path: "risk-insights", - canActivate: [canAccessFeature(FeatureFlag.AccessIntelligence)], + canActivate: [organizationPermissionsGuard((org) => org.useRiskInsights)], component: RiskInsightsComponent, data: { titleId: "RiskInsights", diff --git a/bitwarden_license/bit-web/tsconfig.json b/bitwarden_license/bit-web/tsconfig.json index 3ccdade273e..09de92d355d 100644 --- a/bitwarden_license/bit-web/tsconfig.json +++ b/bitwarden_license/bit-web/tsconfig.json @@ -46,6 +46,7 @@ "../../apps/web/src/**/*.spec.ts", "../../libs/common/src/platform/services/**/*.worker.ts", - "src/**/*.stories.ts" + "src/**/*.stories.ts", + "src/**/*.spec.ts" ] } diff --git a/libs/angular/src/vault/components/add-edit.component.ts b/libs/angular/src/vault/components/add-edit.component.ts index 3747236d51d..1bc58368d8f 100644 --- a/libs/angular/src/vault/components/add-edit.component.ts +++ b/libs/angular/src/vault/components/add-edit.component.ts @@ -699,7 +699,6 @@ export class AddEditComponent implements OnInit, OnDestroy { } protected saveCipher(cipher: Cipher) { - const isNotClone = this.editMode && !this.cloneMode; let orgAdmin = this.organization?.canEditAllCiphers; // if a cipher is unassigned we want to check if they are an admin or have permission to edit any collection @@ -709,7 +708,7 @@ export class AddEditComponent implements OnInit, OnDestroy { return this.cipher.id == null ? this.cipherService.createWithServer(cipher, orgAdmin) - : this.cipherService.updateWithServer(cipher, orgAdmin, isNotClone); + : this.cipherService.updateWithServer(cipher, orgAdmin); } protected deleteCipher() { diff --git a/libs/common/src/admin-console/models/data/organization.data.spec.ts b/libs/common/src/admin-console/models/data/organization.data.spec.ts index 12c5339a245..da9a82e7c5c 100644 --- a/libs/common/src/admin-console/models/data/organization.data.spec.ts +++ b/libs/common/src/admin-console/models/data/organization.data.spec.ts @@ -56,6 +56,7 @@ describe("ORGANIZATIONS state", () => { allowAdminAccessToAllCollectionItems: false, familySponsorshipLastSyncDate: new Date(), userIsManagedByOrganization: false, + useRiskInsights: false, }, }; const result = sut.deserializer(JSON.parse(JSON.stringify(expectedResult))); diff --git a/libs/common/src/admin-console/models/data/organization.data.ts b/libs/common/src/admin-console/models/data/organization.data.ts index d8032fd6fcf..23a0f360f9e 100644 --- a/libs/common/src/admin-console/models/data/organization.data.ts +++ b/libs/common/src/admin-console/models/data/organization.data.ts @@ -56,6 +56,7 @@ export class OrganizationData { limitCollectionDeletion: boolean; allowAdminAccessToAllCollectionItems: boolean; userIsManagedByOrganization: boolean; + useRiskInsights: boolean; constructor( response?: ProfileOrganizationResponse, @@ -116,6 +117,7 @@ export class OrganizationData { this.limitCollectionDeletion = response.limitCollectionDeletion; this.allowAdminAccessToAllCollectionItems = response.allowAdminAccessToAllCollectionItems; this.userIsManagedByOrganization = response.userIsManagedByOrganization; + this.useRiskInsights = response.useRiskInsights; this.isMember = options.isMember; this.isProviderUser = options.isProviderUser; diff --git a/libs/common/src/admin-console/models/domain/organization.ts b/libs/common/src/admin-console/models/domain/organization.ts index 497d3b0889b..3f3ec0f4457 100644 --- a/libs/common/src/admin-console/models/domain/organization.ts +++ b/libs/common/src/admin-console/models/domain/organization.ts @@ -81,6 +81,7 @@ export class Organization { * matches one of the verified domains of that organization, and the user is a member of it. */ userIsManagedByOrganization: boolean; + useRiskInsights: boolean; constructor(obj?: OrganizationData) { if (obj == null) { @@ -137,6 +138,7 @@ export class Organization { this.limitCollectionDeletion = obj.limitCollectionDeletion; this.allowAdminAccessToAllCollectionItems = obj.allowAdminAccessToAllCollectionItems; this.userIsManagedByOrganization = obj.userIsManagedByOrganization; + this.useRiskInsights = obj.useRiskInsights; } get canAccess() { diff --git a/libs/common/src/admin-console/models/response/organization.response.ts b/libs/common/src/admin-console/models/response/organization.response.ts index e033646797d..fd460896a31 100644 --- a/libs/common/src/admin-console/models/response/organization.response.ts +++ b/libs/common/src/admin-console/models/response/organization.response.ts @@ -35,6 +35,7 @@ export class OrganizationResponse extends BaseResponse { limitCollectionCreation: boolean; limitCollectionDeletion: boolean; allowAdminAccessToAllCollectionItems: boolean; + useRiskInsights: boolean; constructor(response: any) { super(response); @@ -75,5 +76,6 @@ export class OrganizationResponse extends BaseResponse { this.allowAdminAccessToAllCollectionItems = this.getResponseProperty( "AllowAdminAccessToAllCollectionItems", ); + this.useRiskInsights = this.getResponseProperty("UseRiskInsights"); } } diff --git a/libs/common/src/admin-console/models/response/profile-organization.response.ts b/libs/common/src/admin-console/models/response/profile-organization.response.ts index ed1f45d4917..9c4b8885ab8 100644 --- a/libs/common/src/admin-console/models/response/profile-organization.response.ts +++ b/libs/common/src/admin-console/models/response/profile-organization.response.ts @@ -53,6 +53,7 @@ export class ProfileOrganizationResponse extends BaseResponse { limitCollectionDeletion: boolean; allowAdminAccessToAllCollectionItems: boolean; userIsManagedByOrganization: boolean; + useRiskInsights: boolean; constructor(response: any) { super(response); @@ -117,5 +118,6 @@ export class ProfileOrganizationResponse extends BaseResponse { "AllowAdminAccessToAllCollectionItems", ); this.userIsManagedByOrganization = this.getResponseProperty("UserIsManagedByOrganization"); + this.useRiskInsights = this.getResponseProperty("UseRiskInsights"); } } diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index 6b8af3ef78d..66d6b155e90 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -31,8 +31,7 @@ export enum FeatureFlag { CipherKeyEncryption = "cipher-key-encryption", VerifiedSsoDomainEndpoint = "pm-12337-refactor-sso-details-endpoint", PM11901_RefactorSelfHostingLicenseUploader = "PM-11901-refactor-self-hosting-license-uploader", - AccessIntelligence = "pm-13227-access-intelligence", - Pm13322AddPolicyDefinitions = "pm-13322-add-policy-definitions", + PM14505AdminConsoleIntegrationPage = "pm-14505-admin-console-integration-page", CriticalApps = "pm-14466-risk-insights-critical-application", TrialPaymentOptional = "PM-8163-trial-payment", SecurityTasks = "security-tasks", @@ -81,8 +80,7 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.CipherKeyEncryption]: FALSE, [FeatureFlag.VerifiedSsoDomainEndpoint]: FALSE, [FeatureFlag.PM11901_RefactorSelfHostingLicenseUploader]: FALSE, - [FeatureFlag.AccessIntelligence]: FALSE, - [FeatureFlag.Pm13322AddPolicyDefinitions]: FALSE, + [FeatureFlag.PM14505AdminConsoleIntegrationPage]: FALSE, [FeatureFlag.CriticalApps]: FALSE, [FeatureFlag.TrialPaymentOptional]: FALSE, [FeatureFlag.SecurityTasks]: FALSE, diff --git a/libs/common/src/enums/integration-type.enum.ts b/libs/common/src/enums/integration-type.enum.ts index acb95106976..42c385fe715 100644 --- a/libs/common/src/enums/integration-type.enum.ts +++ b/libs/common/src/enums/integration-type.enum.ts @@ -1,4 +1,9 @@ export enum IntegrationType { Integration = "integration", SDK = "sdk", + SSO = "sso", + SCIM = "scim", + BWDC = "bwdc", + EVENT = "event", + DEVICE = "device", } diff --git a/libs/common/src/vault/services/cipher.service.spec.ts b/libs/common/src/vault/services/cipher.service.spec.ts index 961bc03bbb0..6b225af0d84 100644 --- a/libs/common/src/vault/services/cipher.service.spec.ts +++ b/libs/common/src/vault/services/cipher.service.spec.ts @@ -229,11 +229,11 @@ describe("Cipher Service", () => { }); describe("updateWithServer()", () => { - it("should call apiService.putCipherAdmin when orgAdmin and isNotClone params are true", async () => { + it("should call apiService.putCipherAdmin when orgAdmin param is true", async () => { const spy = jest .spyOn(apiService, "putCipherAdmin") .mockImplementation(() => Promise.resolve(cipherObj.toCipherData())); - await cipherService.updateWithServer(cipherObj, true, true); + await cipherService.updateWithServer(cipherObj, true); const expectedObj = new CipherRequest(cipherObj); expect(spy).toHaveBeenCalled(); @@ -252,7 +252,7 @@ describe("Cipher Service", () => { expect(spy).toHaveBeenCalledWith(cipherObj.id, expectedObj); }); - it("should call apiService.putPartialCipher when orgAdmin, isNotClone, and edit are false", async () => { + it("should call apiService.putPartialCipher when orgAdmin, and edit are false", async () => { cipherObj.edit = false; const spy = jest .spyOn(apiService, "putPartialCipher") diff --git a/libs/common/src/vault/services/cipher.service.ts b/libs/common/src/vault/services/cipher.service.ts index 474976932e8..19a6e63e5b5 100644 --- a/libs/common/src/vault/services/cipher.service.ts +++ b/libs/common/src/vault/services/cipher.service.ts @@ -709,13 +709,9 @@ export class CipherService implements CipherServiceAbstraction { return new Cipher(updated[cipher.id as CipherId]); } - async updateWithServer( - cipher: Cipher, - orgAdmin?: boolean, - isNotClone?: boolean, - ): Promise { + async updateWithServer(cipher: Cipher, orgAdmin?: boolean): Promise { let response: CipherResponse; - if (orgAdmin && isNotClone) { + if (orgAdmin) { const request = new CipherRequest(cipher); response = await this.apiService.putCipherAdmin(cipher.id, request); const data = new CipherData(response, cipher.collectionIds); diff --git a/libs/node/src/services/node-crypto-function.service.ts b/libs/node/src/services/node-crypto-function.service.ts index c8d676eeaa5..b4126b0813f 100644 --- a/libs/node/src/services/node-crypto-function.service.ts +++ b/libs/node/src/services/node-crypto-function.service.ts @@ -1,6 +1,5 @@ import * as crypto from "crypto"; -import * as argon2 from "argon2"; import * as forge from "node-forge"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; @@ -40,6 +39,7 @@ export class NodeCryptoFunctionService implements CryptoFunctionService { const nodePassword = this.toNodeValue(password); const nodeSalt = this.toNodeBuffer(this.toUint8Buffer(salt)); + const argon2 = await import("argon2"); const hash = await argon2.hash(nodePassword, { salt: nodeSalt, raw: true, diff --git a/libs/tools/send/send-ui/src/new-send-dropdown/new-send-dropdown.component.html b/libs/tools/send/send-ui/src/new-send-dropdown/new-send-dropdown.component.html index 9a65f7d98c5..344348f6a90 100644 --- a/libs/tools/send/send-ui/src/new-send-dropdown/new-send-dropdown.component.html +++ b/libs/tools/send/send-ui/src/new-send-dropdown/new-send-dropdown.component.html @@ -5,7 +5,7 @@ diff --git a/libs/vault/src/cipher-form/services/default-cipher-form.service.ts b/libs/vault/src/cipher-form/services/default-cipher-form.service.ts index 1b7e86f82a7..086ae375dac 100644 --- a/libs/vault/src/cipher-form/services/default-cipher-form.service.ts +++ b/libs/vault/src/cipher-form/services/default-cipher-form.service.ts @@ -71,7 +71,6 @@ export class DefaultCipherFormService implements CipherFormService { await this.cipherService.updateWithServer( encryptedCipher, config.admin || originalCollectionIds.size === 0, - config.mode !== "clone", ); // Then save the new collection changes separately diff --git a/libs/vault/src/components/can-delete-cipher.directive.ts b/libs/vault/src/components/can-delete-cipher.directive.ts new file mode 100644 index 00000000000..c1c7706a1fa --- /dev/null +++ b/libs/vault/src/components/can-delete-cipher.directive.ts @@ -0,0 +1,42 @@ +import { Directive, Input, OnDestroy, TemplateRef, ViewContainerRef } from "@angular/core"; +import { Subject, takeUntil } from "rxjs"; + +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service"; + +/** + * Only shows the element if the user can delete the cipher. + */ +@Directive({ + selector: "[appCanDeleteCipher]", + standalone: true, +}) +export class CanDeleteCipherDirective implements OnDestroy { + private destroy$ = new Subject(); + + @Input("appCanDeleteCipher") set cipher(cipher: CipherView) { + this.viewContainer.clear(); + + this.cipherAuthorizationService + .canDeleteCipher$(cipher) + .pipe(takeUntil(this.destroy$)) + .subscribe((canDelete: boolean) => { + if (canDelete) { + this.viewContainer.createEmbeddedView(this.templateRef); + } else { + this.viewContainer.clear(); + } + }); + } + + constructor( + private templateRef: TemplateRef, + private viewContainer: ViewContainerRef, + private cipherAuthorizationService: CipherAuthorizationService, + ) {} + + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } +} diff --git a/libs/vault/src/index.ts b/libs/vault/src/index.ts index f6a95281f81..dca9b2dee79 100644 --- a/libs/vault/src/index.ts +++ b/libs/vault/src/index.ts @@ -2,6 +2,7 @@ export { PasswordRepromptService } from "./services/password-reprompt.service"; export { CopyCipherFieldService, CopyAction } from "./services/copy-cipher-field.service"; export { CopyCipherFieldDirective } from "./components/copy-cipher-field.directive"; export { OrgIconDirective } from "./components/org-icon.directive"; +export { CanDeleteCipherDirective } from "./components/can-delete-cipher.directive"; export * from "./cipher-view"; export * from "./cipher-form";