mirror of
https://github.com/bitwarden/browser.git
synced 2024-11-14 10:26:19 +01:00
Merge branch 'main' into pm-13868-remove-upgrade-password-manager-feature-flag
This commit is contained in:
commit
6a8f69ba50
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
@ -71,6 +71,7 @@ bitwarden_license/bit-web/src/app/billing @bitwarden/team-billing-dev
|
||||
## Platform team files ##
|
||||
apps/browser/src/platform @bitwarden/team-platform-dev
|
||||
apps/cli/src/platform @bitwarden/team-platform-dev
|
||||
apps/desktop/macos @bitwarden/team-platform-dev
|
||||
apps/desktop/src/platform @bitwarden/team-platform-dev
|
||||
apps/web/src/app/platform @bitwarden/team-platform-dev
|
||||
libs/angular/src/platform @bitwarden/team-platform-dev
|
||||
@ -91,6 +92,7 @@ apps/web/src/translation-constants.ts @bitwarden/team-platform-dev
|
||||
apps/browser/src/autofill @bitwarden/team-autofill-dev
|
||||
apps/desktop/src/autofill @bitwarden/team-autofill-dev
|
||||
libs/common/src/autofill @bitwarden/team-autofill-dev
|
||||
apps/desktop/macos/autofill-extension @bitwarden/team-autofill-dev
|
||||
# DuckDuckGo integration
|
||||
apps/desktop/native-messaging-test-runner @bitwarden/team-autofill-dev
|
||||
apps/desktop/src/services/native-message-handler.service.ts @bitwarden/team-autofill-dev
|
||||
|
35
.github/workflows/build-desktop.yml
vendored
35
.github/workflows/build-desktop.yml
vendored
@ -166,7 +166,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
|
||||
sudo apt-get -y install pkg-config libxss-dev libsecret-1-dev rpm musl-dev musl-tools flatpak flatpak-builder
|
||||
|
||||
- name: Set up Snap
|
||||
run: sudo snap install snapcraft --classic
|
||||
@ -248,6 +248,19 @@ jobs:
|
||||
name: ${{ needs.setup.outputs.release_channel }}-linux.yml
|
||||
path: apps/desktop/dist/${{ needs.setup.outputs.release_channel }}-linux.yml
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Build flatpak
|
||||
working-directory: apps/desktop
|
||||
run: |
|
||||
sudo flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
|
||||
sudo npm run pack:lin:flatpak
|
||||
|
||||
- name: Upload flatpak artifact
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
||||
with:
|
||||
name: com.bitwarden.desktop.flatpak
|
||||
path: apps/desktop/dist/com.bitwarden.desktop.flatpak
|
||||
if-no-files-found: error
|
||||
|
||||
|
||||
windows:
|
||||
@ -1164,6 +1177,21 @@ jobs:
|
||||
--file $HOME/secrets/bitwarden_desktop_appstore.provisionprofile \
|
||||
--output none
|
||||
|
||||
az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \
|
||||
--name bitwarden_desktop_autofill_app_store_2024.provisionprofile \
|
||||
--file $HOME/secrets/bitwarden_desktop_autofill_app_store_2024.provisionprofile \
|
||||
--output none
|
||||
|
||||
- name: Set up provisioning profiles
|
||||
run: |
|
||||
AUTOFILL_PROFILE_PATH=$HOME/secrets/bitwarden_desktop_autofill_app_store_2024.provisionprofile
|
||||
PROFILES_DIR_PATH=$HOME/Library/MobileDevice/Provisioning\ Profiles
|
||||
|
||||
mkdir -p "$PROFILES_DIR_PATH"
|
||||
|
||||
AUTOFILL_UUID=$(grep UUID -A1 -a $AUTOFILL_PROFILE_PATH | grep -io "[-A-F0-9]\{36\}")
|
||||
cp $AUTOFILL_PROFILE_PATH "$PROFILES_DIR_PATH/$AUTOFILL_UUID.provisionprofile"
|
||||
|
||||
- name: Get certificates
|
||||
run: |
|
||||
mkdir -p $HOME/certificates
|
||||
@ -1215,11 +1243,6 @@ jobs:
|
||||
|
||||
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $KEYCHAIN_PASSWORD build.keychain
|
||||
|
||||
- name: Set up provisioning profiles
|
||||
run: |
|
||||
cp $HOME/secrets/bitwarden_desktop_appstore.provisionprofile \
|
||||
$GITHUB_WORKSPACE/apps/desktop/bitwarden_desktop_appstore.provisionprofile
|
||||
|
||||
- name: Increment version
|
||||
shell: pwsh
|
||||
env:
|
||||
|
2
.github/workflows/build-web.yml
vendored
2
.github/workflows/build-web.yml
vendored
@ -216,7 +216,7 @@ jobs:
|
||||
- name: Generate Docker image tag
|
||||
id: tag
|
||||
run: |
|
||||
if [[ $(grep "pull" <<< "${GITHUB_REF}") ]]; then
|
||||
if [[ "${GITHUB_EVENT_NAME}" == "pull_request_target" ]]; then
|
||||
IMAGE_TAG=$(echo "${GITHUB_HEAD_REF}" | sed "s#/#-#g")
|
||||
else
|
||||
IMAGE_TAG=$(echo "${GITHUB_REF_NAME}" | sed "s#/#-#g")
|
||||
|
1
.github/workflows/lint.yml
vendored
1
.github/workflows/lint.yml
vendored
@ -36,6 +36,7 @@ jobs:
|
||||
! -path "./.github/*" \
|
||||
! -path "*/Cargo.toml" \
|
||||
! -path "*/Cargo.lock" \
|
||||
! -path "./apps/desktop/macos/*" \
|
||||
> tmp.txt
|
||||
diff <(sort .github/whitelist-capital-letters.txt) <(sort tmp.txt)
|
||||
|
||||
|
35
.github/workflows/repository-management.yml
vendored
35
.github/workflows/repository-management.yml
vendored
@ -44,7 +44,6 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
outputs:
|
||||
branch: ${{ steps.set-branch.outputs.branch }}
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
steps:
|
||||
- name: Set branch
|
||||
id: set-branch
|
||||
@ -59,13 +58,6 @@ jobs:
|
||||
|
||||
echo "branch=$BRANCH" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Generate GH App token
|
||||
uses: actions/create-github-app-token@5d869da34e18e7287c1daad50e0b8ea0f506ce69 # v1.11.0
|
||||
id: app-token
|
||||
with:
|
||||
app-id: ${{ secrets.BW_GHAPP_ID }}
|
||||
private-key: ${{ secrets.BW_GHAPP_KEY }}
|
||||
|
||||
|
||||
cut_branch:
|
||||
name: Cut branch
|
||||
@ -73,11 +65,18 @@ jobs:
|
||||
needs: setup
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Generate GH App token
|
||||
uses: actions/create-github-app-token@5d869da34e18e7287c1daad50e0b8ea0f506ce69 # v1.11.0
|
||||
id: app-token
|
||||
with:
|
||||
app-id: ${{ secrets.BW_GHAPP_ID }}
|
||||
private-key: ${{ secrets.BW_GHAPP_KEY }}
|
||||
|
||||
- name: Check out target ref
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
ref: ${{ inputs.target_ref }}
|
||||
token: ${{ needs.setup.outputs.token }}
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
|
||||
- name: Check if ${{ needs.setup.outputs.branch }} branch exists
|
||||
env:
|
||||
@ -115,11 +114,18 @@ jobs:
|
||||
with:
|
||||
version: ${{ inputs.version_number_override }}
|
||||
|
||||
- name: Generate GH App token
|
||||
uses: actions/create-github-app-token@5d869da34e18e7287c1daad50e0b8ea0f506ce69 # v1.11.0
|
||||
id: app-token
|
||||
with:
|
||||
app-id: ${{ secrets.BW_GHAPP_ID }}
|
||||
private-key: ${{ secrets.BW_GHAPP_KEY }}
|
||||
|
||||
- name: Check out branch
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
ref: main
|
||||
token: ${{ needs.setup.outputs.token }}
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
|
||||
- name: Configure Git
|
||||
run: |
|
||||
@ -445,11 +451,18 @@ jobs:
|
||||
- bump_version
|
||||
- setup
|
||||
steps:
|
||||
- name: Generate GH App token
|
||||
uses: actions/create-github-app-token@5d869da34e18e7287c1daad50e0b8ea0f506ce69 # v1.11.0
|
||||
id: app-token
|
||||
with:
|
||||
app-id: ${{ secrets.BW_GHAPP_ID }}
|
||||
private-key: ${{ secrets.BW_GHAPP_KEY }}
|
||||
|
||||
- name: Check out main branch
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
ref: main
|
||||
token: ${{ needs.setup.outputs.token }}
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
|
||||
- name: Configure Git
|
||||
run: |
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -27,6 +27,9 @@ npm-debug.log
|
||||
dist
|
||||
build
|
||||
.angular/cache
|
||||
.flatpak
|
||||
.flatpak-repo
|
||||
.flatpak-builder
|
||||
|
||||
# Testing
|
||||
coverage
|
||||
|
@ -1,4 +1,4 @@
|
||||
<popup-page>
|
||||
<popup-page [disablePadding]="true">
|
||||
<popup-header
|
||||
slot="header"
|
||||
[background]="'alt'"
|
||||
@ -19,7 +19,6 @@
|
||||
[icon]="pageIcon"
|
||||
[showReadonlyHostname]="showReadonlyHostname"
|
||||
[hideLogo]="true"
|
||||
[decreaseTopPadding]="true"
|
||||
[maxWidth]="maxWidth"
|
||||
>
|
||||
<router-outlet></router-outlet>
|
||||
|
@ -376,6 +376,7 @@ export default class MainBackground {
|
||||
autoSubmitLoginBackground: AutoSubmitLoginBackground;
|
||||
sdkService: SdkService;
|
||||
cipherAuthorizationService: CipherAuthorizationService;
|
||||
inlineMenuFieldQualificationService: InlineMenuFieldQualificationService;
|
||||
|
||||
onUpdatedRan: boolean;
|
||||
onReplacedRan: boolean;
|
||||
@ -1249,6 +1250,8 @@ export default class MainBackground {
|
||||
this.collectionService,
|
||||
this.organizationService,
|
||||
);
|
||||
|
||||
this.inlineMenuFieldQualificationService = new InlineMenuFieldQualificationService();
|
||||
}
|
||||
|
||||
async bootstrap() {
|
||||
@ -1630,7 +1633,6 @@ export default class MainBackground {
|
||||
this.themeStateService,
|
||||
);
|
||||
} else {
|
||||
const inlineMenuFieldQualificationService = new InlineMenuFieldQualificationService();
|
||||
this.overlayBackground = new OverlayBackground(
|
||||
this.logService,
|
||||
this.cipherService,
|
||||
@ -1643,7 +1645,7 @@ export default class MainBackground {
|
||||
this.platformUtilsService,
|
||||
this.vaultSettingsService,
|
||||
this.fido2ActiveRequestManager,
|
||||
inlineMenuFieldQualificationService,
|
||||
this.inlineMenuFieldQualificationService,
|
||||
this.themeStateService,
|
||||
this.totpService,
|
||||
() => this.generatePassword(),
|
||||
|
@ -113,6 +113,7 @@ import { ExtensionAnonLayoutWrapperDataService } from "../../auth/popup/extensio
|
||||
import { ExtensionLoginComponentService } from "../../auth/popup/login/extension-login-component.service";
|
||||
import { AutofillService as AutofillServiceAbstraction } from "../../autofill/services/abstractions/autofill.service";
|
||||
import AutofillService from "../../autofill/services/autofill.service";
|
||||
import { InlineMenuFieldQualificationService } from "../../autofill/services/inline-menu-field-qualification.service";
|
||||
import { ForegroundBrowserBiometricsService } from "../../key-management/biometrics/foreground-browser-biometrics";
|
||||
import { BrowserKeyService } from "../../key-management/browser-key.service";
|
||||
import { BrowserApi } from "../../platform/browser/browser-api";
|
||||
@ -167,6 +168,7 @@ const safeProviders: SafeProvider[] = [
|
||||
safeProvider(DebounceNavigationService),
|
||||
safeProvider(DialogService),
|
||||
safeProvider(PopupCloseWarningService),
|
||||
safeProvider(InlineMenuFieldQualificationService),
|
||||
safeProvider({
|
||||
provide: DEFAULT_VAULT_TIMEOUT,
|
||||
useValue: VaultTimeoutStringType.OnRestart,
|
||||
|
@ -6,7 +6,7 @@
|
||||
<app-current-account></app-current-account>
|
||||
</ng-container>
|
||||
</popup-header>
|
||||
<div slot="above-scroll-area" class="tw-p-4">
|
||||
<div slot="above-scroll-area" class="tw-p-4" *ngIf="!(sendsLoading$ | async)">
|
||||
<bit-callout *ngIf="sendsDisabled" [title]="'sendDisabled' | i18n">
|
||||
{{ "sendDisabledWarning" | i18n }}
|
||||
</bit-callout>
|
||||
|
@ -5,6 +5,7 @@ import { Subject } from "rxjs";
|
||||
|
||||
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { AUTOFILL_ID } from "@bitwarden/common/autofill/constants";
|
||||
import { EventType } from "@bitwarden/common/enums";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
@ -33,6 +34,7 @@ describe("ViewV2Component", () => {
|
||||
const params$ = new Subject();
|
||||
const mockNavigate = jest.fn();
|
||||
const collect = jest.fn().mockResolvedValue(null);
|
||||
const doAutofill = jest.fn();
|
||||
|
||||
const mockCipher = {
|
||||
id: "122-333-444",
|
||||
@ -41,7 +43,7 @@ describe("ViewV2Component", () => {
|
||||
};
|
||||
|
||||
const mockVaultPopupAutofillService = {
|
||||
doAutofill: jest.fn(),
|
||||
doAutofill,
|
||||
};
|
||||
const mockUserId = Utils.newGuid() as UserId;
|
||||
const accountService: FakeAccountService = mockAccountServiceWith(mockUserId);
|
||||
@ -54,6 +56,7 @@ describe("ViewV2Component", () => {
|
||||
beforeEach(async () => {
|
||||
mockNavigate.mockClear();
|
||||
collect.mockClear();
|
||||
doAutofill.mockClear();
|
||||
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [ViewV2Component],
|
||||
@ -148,5 +151,13 @@ describe("ViewV2Component", () => {
|
||||
undefined,
|
||||
);
|
||||
}));
|
||||
|
||||
it('invokes `doAutofill` when action="AUTOFILL_ID"', fakeAsync(() => {
|
||||
params$.next({ action: AUTOFILL_ID });
|
||||
|
||||
flush(); // Resolve all promises
|
||||
|
||||
expect(doAutofill).toHaveBeenCalledOnce();
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
@ -100,7 +100,7 @@ export class ViewV2Component {
|
||||
switchMap(async (cipher) => {
|
||||
this.cipher = cipher;
|
||||
this.headerText = this.setHeader(cipher.type);
|
||||
if (this.loadAction === AUTOFILL_ID || this.loadAction === SHOW_AUTOFILL_BUTTON) {
|
||||
if (this.loadAction === AUTOFILL_ID) {
|
||||
await this.vaultPopupAutofillService.doAutofill(this.cipher);
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,7 @@ import { BehaviorSubject, of } from "rxjs";
|
||||
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
@ -19,6 +20,7 @@ import { LoginView } from "@bitwarden/common/vault/models/view/login.view";
|
||||
import { ToastService } from "@bitwarden/components";
|
||||
import { PasswordRepromptService } from "@bitwarden/vault";
|
||||
|
||||
import { InlineMenuFieldQualificationService } from "../../../../../browser/src/autofill/services/inline-menu-field-qualification.service";
|
||||
import {
|
||||
AutoFillOptions,
|
||||
AutofillService,
|
||||
@ -46,6 +48,8 @@ describe("VaultPopupAutofillService", () => {
|
||||
const mockPasswordRepromptService = mock<PasswordRepromptService>();
|
||||
const mockCipherService = mock<CipherService>();
|
||||
const mockMessagingService = mock<MessagingService>();
|
||||
const mockInlineMenuFieldQualificationService = mock<InlineMenuFieldQualificationService>();
|
||||
const mockLogService = mock<LogService>();
|
||||
|
||||
const mockUserId = Utils.newGuid() as UserId;
|
||||
const accountService: FakeAccountService = mockAccountServiceWith(mockUserId);
|
||||
@ -53,6 +57,12 @@ describe("VaultPopupAutofillService", () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(BrowserPopupUtils, "inPopout").mockReturnValue(false);
|
||||
jest.spyOn(BrowserApi, "getTabFromCurrentWindow").mockResolvedValue(mockCurrentTab);
|
||||
jest
|
||||
.spyOn(mockInlineMenuFieldQualificationService, "isFieldForCreditCardForm")
|
||||
.mockReturnValue(true);
|
||||
jest
|
||||
.spyOn(mockInlineMenuFieldQualificationService, "isFieldForIdentityForm")
|
||||
.mockReturnValue(true);
|
||||
|
||||
mockAutofillService.collectPageDetailsFromTab$.mockReturnValue(new BehaviorSubject([]));
|
||||
|
||||
@ -70,6 +80,14 @@ describe("VaultPopupAutofillService", () => {
|
||||
provide: AccountService,
|
||||
useValue: accountService,
|
||||
},
|
||||
{
|
||||
provide: InlineMenuFieldQualificationService,
|
||||
useValue: mockInlineMenuFieldQualificationService,
|
||||
},
|
||||
{
|
||||
provide: LogService,
|
||||
useValue: mockLogService,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
|
@ -10,10 +10,12 @@ import {
|
||||
startWith,
|
||||
Subject,
|
||||
switchMap,
|
||||
timeout,
|
||||
} from "rxjs";
|
||||
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
@ -23,6 +25,7 @@ import { LoginUriView } from "@bitwarden/common/vault/models/view/login-uri.view
|
||||
import { ToastService } from "@bitwarden/components";
|
||||
import { PasswordRepromptService } from "@bitwarden/vault";
|
||||
|
||||
import { InlineMenuFieldQualificationService } from "../../../../../browser/src/autofill/services/inline-menu-field-qualification.service";
|
||||
import {
|
||||
AutofillService,
|
||||
PageDetail,
|
||||
@ -72,11 +75,53 @@ export class VaultPopupAutofillService {
|
||||
if (!tab) {
|
||||
return of([]);
|
||||
}
|
||||
return this.autofillService.collectPageDetailsFromTab$(tab);
|
||||
return this.autofillService
|
||||
.collectPageDetailsFromTab$(tab)
|
||||
.pipe(timeout({ first: 1500, with: () => of([]) }));
|
||||
}),
|
||||
shareReplay({ refCount: false, bufferSize: 1 }),
|
||||
);
|
||||
|
||||
nonLoginCipherTypesOnPage$: Observable<{
|
||||
[CipherType.Card]: boolean;
|
||||
[CipherType.Identity]: boolean;
|
||||
}> = this._currentPageDetails$.pipe(
|
||||
map((pageDetails) => {
|
||||
let pageHasCardFields = false;
|
||||
let pageHasIdentityFields = false;
|
||||
|
||||
try {
|
||||
if (!pageDetails) {
|
||||
throw Error("No page details were provided");
|
||||
}
|
||||
|
||||
for (const details of pageDetails) {
|
||||
for (const field of details.details.fields) {
|
||||
if (!pageHasCardFields) {
|
||||
pageHasCardFields = this.inlineMenuFieldQualificationService.isFieldForCreditCardForm(
|
||||
field,
|
||||
details.details,
|
||||
);
|
||||
}
|
||||
|
||||
if (!pageHasIdentityFields) {
|
||||
pageHasIdentityFields =
|
||||
this.inlineMenuFieldQualificationService.isFieldForIdentityForm(
|
||||
field,
|
||||
details.details,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// no-op on failure; do not show extra cipher types
|
||||
this.logService.warning(error.message);
|
||||
}
|
||||
|
||||
return { [CipherType.Card]: pageHasCardFields, [CipherType.Identity]: pageHasIdentityFields };
|
||||
}),
|
||||
);
|
||||
|
||||
constructor(
|
||||
private autofillService: AutofillService,
|
||||
private i18nService: I18nService,
|
||||
@ -87,6 +132,8 @@ export class VaultPopupAutofillService {
|
||||
private messagingService: MessagingService,
|
||||
private route: ActivatedRoute,
|
||||
private accountService: AccountService,
|
||||
private logService: LogService,
|
||||
private inlineMenuFieldQualificationService: InlineMenuFieldQualificationService,
|
||||
) {
|
||||
this._currentPageDetails$.subscribe();
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
|
||||
import { InlineMenuFieldQualificationService } from "../../../../../browser/src/autofill/services/inline-menu-field-qualification.service";
|
||||
import { BrowserApi } from "../../../platform/browser/browser-api";
|
||||
|
||||
import { VaultPopupAutofillService } from "./vault-popup-autofill.service";
|
||||
@ -39,6 +40,7 @@ describe("VaultPopupItemsService", () => {
|
||||
const collectionService = mock<CollectionService>();
|
||||
const vaultAutofillServiceMock = mock<VaultPopupAutofillService>();
|
||||
const syncServiceMock = mock<SyncService>();
|
||||
const inlineMenuFieldQualificationServiceMock = mock<InlineMenuFieldQualificationService>();
|
||||
|
||||
beforeEach(() => {
|
||||
allCiphers = cipherFactory(10);
|
||||
@ -78,6 +80,11 @@ describe("VaultPopupItemsService", () => {
|
||||
url: "https://example.com",
|
||||
} as chrome.tabs.Tab);
|
||||
|
||||
vaultAutofillServiceMock.nonLoginCipherTypesOnPage$ = new BehaviorSubject({
|
||||
[CipherType.Card]: true,
|
||||
[CipherType.Identity]: true,
|
||||
});
|
||||
|
||||
mockOrg = {
|
||||
id: "org1",
|
||||
name: "Organization 1",
|
||||
@ -105,6 +112,10 @@ describe("VaultPopupItemsService", () => {
|
||||
{ provide: CollectionService, useValue: collectionService },
|
||||
{ provide: VaultPopupAutofillService, useValue: vaultAutofillServiceMock },
|
||||
{ provide: SyncService, useValue: syncServiceMock },
|
||||
{
|
||||
provide: InlineMenuFieldQualificationService,
|
||||
useValue: inlineMenuFieldQualificationServiceMock,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
|
@ -62,8 +62,13 @@ export class VaultPopupItemsService {
|
||||
private _otherAutoFillTypes$: Observable<CipherType[]> = combineLatest([
|
||||
this.vaultSettingsService.showCardsCurrentTab$,
|
||||
this.vaultSettingsService.showIdentitiesCurrentTab$,
|
||||
this.vaultPopupAutofillService.nonLoginCipherTypesOnPage$,
|
||||
]).pipe(
|
||||
map(([showCards, showIdentities]) => {
|
||||
map(([showCardsSettingEnabled, showIdentitiesSettingEnabled, nonLoginCipherTypesOnPage]) => {
|
||||
const showCards = showCardsSettingEnabled && nonLoginCipherTypesOnPage[CipherType.Card];
|
||||
const showIdentities =
|
||||
showIdentitiesSettingEnabled && nonLoginCipherTypesOnPage[CipherType.Identity];
|
||||
|
||||
return [
|
||||
...(showCards ? [CipherType.Card] : []),
|
||||
...(showIdentities ? [CipherType.Identity] : []),
|
||||
|
@ -12,7 +12,7 @@
|
||||
|
||||
<Properties>
|
||||
<DisplayName>Bitwarden Password Manager</DisplayName>
|
||||
<PublisherDisplayName>8bit Solutions LLC</PublisherDisplayName>
|
||||
<PublisherDisplayName>Bitwarden Inc</PublisherDisplayName>
|
||||
<Logo>Assets/icon_50.png</Logo>
|
||||
</Properties>
|
||||
|
||||
|
1
apps/desktop/.gitignore
vendored
1
apps/desktop/.gitignore
vendored
@ -2,3 +2,4 @@ dist-safari/
|
||||
*.nupkg
|
||||
*.env
|
||||
PlugIns/safari.appex/
|
||||
xcuserdata/
|
||||
|
@ -162,7 +162,7 @@
|
||||
"applicationId": "bitwardendesktop",
|
||||
"identityName": "8bitSolutionsLLC.bitwardendesktop",
|
||||
"publisher": "CN=14D52771-DE3C-4886-B8BF-825BA7690418",
|
||||
"publisherDisplayName": "8bit Solutions LLC",
|
||||
"publisherDisplayName": "Bitwarden Inc",
|
||||
"languages": [
|
||||
"en-US",
|
||||
"af",
|
||||
|
23
apps/desktop/macos/README.md
Normal file
23
apps/desktop/macos/README.md
Normal file
@ -0,0 +1,23 @@
|
||||
# MacOS Extensions for Desktop Apps
|
||||
|
||||
This folder contains an Xcode project that builds macOS extensions for our desktop app. The extensions are used to provide additional functionality to the desktop app, such as autofill (password and passkeys).
|
||||
|
||||
## Manage loaded extensions
|
||||
|
||||
macOS automatically loads extensions from apps, even if they have never been used (especially if built with Xcode). This can be confusing when you have multiple copies of the same application. To see where an extension is loaded from, use the following command:
|
||||
|
||||
```bash
|
||||
# To list all extensions
|
||||
pluginkit -m -v
|
||||
|
||||
# To list a specific extension
|
||||
pluginkit -m -v -i com.bitwarden.desktop.autofill-extension
|
||||
```
|
||||
|
||||
To unregister an extension, you can either remove it from your filesystem, or use the following command:
|
||||
|
||||
```bash
|
||||
pluginkit -r <path to .appex>
|
||||
```
|
||||
|
||||
where the path to the .appex file can be found in the output of the first command.
|
@ -0,0 +1,69 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="17021" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="17021"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="CredentialProviderViewController" customModuleProvider="target">
|
||||
<connections>
|
||||
<outlet property="view" destination="1" id="2"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<customView translatesAutoresizingMaskIntoConstraints="NO" id="1">
|
||||
<rect key="frame" x="0.0" y="0.0" width="378" height="94"/>
|
||||
<subviews>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="1uM-r7-H1c">
|
||||
<rect key="frame" x="177" y="3" width="197" height="32"/>
|
||||
<buttonCell key="cell" type="push" title="Return Example Password" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="2l4-PO-we5">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<string key="keyEquivalent">D</string>
|
||||
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="passwordSelected:" target="-2" id="yic-EC-GGk"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="NVE-vN-dkz">
|
||||
<rect key="frame" x="99" y="3" width="82" height="32"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="60" id="cP1-hK-9ZX"/>
|
||||
</constraints>
|
||||
<buttonCell key="cell" type="push" title="Cancel" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="6Up-t3-mwm">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<string key="keyEquivalent" base64-UTF8="YES">
|
||||
Gw
|
||||
</string>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="cancel:" target="-2" id="Qav-AK-DGt"/>
|
||||
</connections>
|
||||
</button>
|
||||
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="aNc-0i-CWK">
|
||||
<rect key="frame" x="135" y="63" width="108" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="left" title="autofill-extension" id="0xp-rC-2gr">
|
||||
<font key="font" metaFont="systemBold"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="1uM-r7-H1c" firstAttribute="leading" secondItem="NVE-vN-dkz" secondAttribute="trailing" constant="8" id="1UO-J1-LbJ"/>
|
||||
<constraint firstItem="NVE-vN-dkz" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="1" secondAttribute="leading" constant="20" symbolic="YES" id="3N9-qo-UfS"/>
|
||||
<constraint firstAttribute="bottom" secondItem="1uM-r7-H1c" secondAttribute="bottom" constant="10" id="4wH-De-nMF"/>
|
||||
<constraint firstItem="NVE-vN-dkz" firstAttribute="firstBaseline" secondItem="aNc-0i-CWK" secondAttribute="baseline" constant="50" id="Dpq-cK-cPE"/>
|
||||
<constraint firstAttribute="bottom" secondItem="NVE-vN-dkz" secondAttribute="bottom" constant="10" id="USG-Gg-of3"/>
|
||||
<constraint firstItem="1uM-r7-H1c" firstAttribute="leading" secondItem="NVE-vN-dkz" secondAttribute="trailing" constant="8" id="a8N-vS-Ew9"/>
|
||||
<constraint firstAttribute="trailing" secondItem="1uM-r7-H1c" secondAttribute="trailing" constant="10" id="qfT-cw-QQ2"/>
|
||||
<constraint firstAttribute="centerX" secondItem="aNc-0i-CWK" secondAttribute="centerX" id="uV3-Wn-RA3"/>
|
||||
<constraint firstItem="aNc-0i-CWK" firstAttribute="top" secondItem="1" secondAttribute="top" constant="15" id="vpR-tf-ebx"/>
|
||||
</constraints>
|
||||
<point key="canvasLocation" x="162" y="146"/>
|
||||
</customView>
|
||||
</objects>
|
||||
</document>
|
@ -0,0 +1,93 @@
|
||||
//
|
||||
// CredentialProviderViewController.swift
|
||||
// autofill-extension
|
||||
//
|
||||
// Created by Andreas Coroiu on 2023-12-21.
|
||||
//
|
||||
|
||||
import AuthenticationServices
|
||||
import os
|
||||
|
||||
class CredentialProviderViewController: ASCredentialProviderViewController {
|
||||
let logger = Logger()
|
||||
|
||||
/*
|
||||
Implement this method if your extension supports showing credentials in the QuickType bar.
|
||||
When the user selects a credential from your app, this method will be called with the
|
||||
ASPasswordCredentialIdentity your app has previously saved to the ASCredentialIdentityStore.
|
||||
Provide the password by completing the extension request with the associated ASPasswordCredential.
|
||||
If using the credential would require showing custom UI for authenticating the user, cancel
|
||||
the request with error code ASExtensionError.userInteractionRequired.
|
||||
|
||||
override func provideCredentialWithoutUserInteraction(for credentialIdentity: ASPasswordCredentialIdentity) {
|
||||
let databaseIsUnlocked = true
|
||||
if (databaseIsUnlocked) {
|
||||
let passwordCredential = ASPasswordCredential(user: "j_appleseed", password: "apple1234")
|
||||
self.extensionContext.completeRequest(withSelectedCredential: passwordCredential, completionHandler: nil)
|
||||
} else {
|
||||
self.extensionContext.cancelRequest(withError: NSError(domain: ASExtensionErrorDomain, code:ASExtensionError.userInteractionRequired.rawValue))
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
Implement this method if provideCredentialWithoutUserInteraction(for:) can fail with
|
||||
ASExtensionError.userInteractionRequired. In this case, the system may present your extension's
|
||||
UI and call this method. Show appropriate UI for authenticating the user then provide the password
|
||||
by completing the extension request with the associated ASPasswordCredential.
|
||||
|
||||
override func prepareInterfaceToProvideCredential(for credentialIdentity: ASPasswordCredentialIdentity) {
|
||||
}
|
||||
*/
|
||||
|
||||
@IBAction func cancel(_ sender: AnyObject?) {
|
||||
self.extensionContext.cancelRequest(withError: NSError(domain: ASExtensionErrorDomain, code: ASExtensionError.userCanceled.rawValue))
|
||||
}
|
||||
|
||||
@IBAction func passwordSelected(_ sender: AnyObject?) {
|
||||
let passwordCredential = ASPasswordCredential(user: "j_appleseed", password: "apple1234")
|
||||
self.extensionContext.completeRequest(withSelectedCredential: passwordCredential, completionHandler: nil)
|
||||
}
|
||||
|
||||
override func prepareInterfaceForExtensionConfiguration() {
|
||||
logger.log("[autofill-extension] prepareInterfaceForExtensionConfiguration called")
|
||||
}
|
||||
|
||||
override func prepareInterface(forPasskeyRegistration registrationRequest: ASCredentialRequest) {
|
||||
logger.log("[autofill-extension] prepare interface for registration request \(registrationRequest.description)")
|
||||
|
||||
// self.extensionContext.cancelRequest(withError: ExampleError.nope)
|
||||
}
|
||||
|
||||
override func prepareInterfaceToProvideCredential(for credentialRequest: ASCredentialRequest) {
|
||||
logger.log("[autofill-extension] prepare interface for credential request \(credentialRequest.description)")
|
||||
}
|
||||
|
||||
/*
|
||||
Prepare your UI to list available credentials for the user to choose from. The items in
|
||||
'serviceIdentifiers' describe the service the user is logging in to, so your extension can
|
||||
prioritize the most relevant credentials in the list.
|
||||
*/
|
||||
override func prepareCredentialList(for serviceIdentifiers: [ASCredentialServiceIdentifier]) {
|
||||
logger.log("[autofill-extension] prepareCredentialList for serviceIdentifiers: \(serviceIdentifiers.count)")
|
||||
|
||||
for serviceIdentifier in serviceIdentifiers {
|
||||
logger.log(" service: \(serviceIdentifier.identifier)")
|
||||
}
|
||||
}
|
||||
|
||||
override func prepareInterfaceToProvideCredential(for credentialIdentity: ASPasswordCredentialIdentity) {
|
||||
logger.log("[autofill-extension] prepareInterfaceToProvideCredential for credentialIdentity: \(credentialIdentity.user)")
|
||||
}
|
||||
|
||||
override func prepareCredentialList(for serviceIdentifiers: [ASCredentialServiceIdentifier], requestParameters: ASPasskeyCredentialRequestParameters) {
|
||||
logger.log("[autofill-extension] prepareCredentialList(passkey) for serviceIdentifiers: \(serviceIdentifiers.count)")
|
||||
|
||||
for serviceIdentifier in serviceIdentifiers {
|
||||
logger.log(" service: \(serviceIdentifier.identifier)")
|
||||
}
|
||||
|
||||
logger.log("request parameters: \(requestParameters.relyingPartyIdentifier)")
|
||||
}
|
||||
|
||||
}
|
23
apps/desktop/macos/autofill-extension/Info.plist
Normal file
23
apps/desktop/macos/autofill-extension/Info.plist
Normal file
@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionAttributes</key>
|
||||
<dict>
|
||||
<key>ASCredentialProviderExtensionCapabilities</key>
|
||||
<dict>
|
||||
<key>ProvidesPasskeys</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>ASCredentialProviderExtensionShowsConfigurationUI</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
<string>com.apple.authentication-services-credential-provider-ui</string>
|
||||
<key>NSExtensionPrincipalClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).CredentialProviderViewController</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.developer.authentication-services.autofill-credential-provider</key>
|
||||
<true/>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
367
apps/desktop/macos/desktop.xcodeproj/project.pbxproj
Normal file
367
apps/desktop/macos/desktop.xcodeproj/project.pbxproj
Normal file
@ -0,0 +1,367 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 56;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
E1DF713F2B342F6900F29026 /* AuthenticationServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E1DF713E2B342F6900F29026 /* AuthenticationServices.framework */; };
|
||||
E1DF71422B342F6900F29026 /* CredentialProviderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1DF71412B342F6900F29026 /* CredentialProviderViewController.swift */; };
|
||||
E1DF71452B342F6900F29026 /* CredentialProviderViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = E1DF71432B342F6900F29026 /* CredentialProviderViewController.xib */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
968ED08A2C52A47200FFFEE6 /* Production.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Production.xcconfig; sourceTree = "<group>"; };
|
||||
E1DF713C2B342F6900F29026 /* autofill-extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "autofill-extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
E1DF713E2B342F6900F29026 /* AuthenticationServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AuthenticationServices.framework; path = System/Library/Frameworks/AuthenticationServices.framework; sourceTree = SDKROOT; };
|
||||
E1DF71412B342F6900F29026 /* CredentialProviderViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CredentialProviderViewController.swift; sourceTree = "<group>"; };
|
||||
E1DF71442B342F6900F29026 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/CredentialProviderViewController.xib; sourceTree = "<group>"; };
|
||||
E1DF71462B342F6900F29026 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
E1DF71472B342F6900F29026 /* autofill_extension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = autofill_extension.entitlements; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
E1DF71392B342F6900F29026 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
E1DF713F2B342F6900F29026 /* AuthenticationServices.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
E1DF711D2B342E2800F29026 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
968ED08A2C52A47200FFFEE6 /* Production.xcconfig */,
|
||||
E1DF71402B342F6900F29026 /* autofill-extension */,
|
||||
E1DF713D2B342F6900F29026 /* Frameworks */,
|
||||
E1DF71272B342E2800F29026 /* Products */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
E1DF71272B342E2800F29026 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E1DF713C2B342F6900F29026 /* autofill-extension.appex */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
E1DF713D2B342F6900F29026 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E1DF713E2B342F6900F29026 /* AuthenticationServices.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
E1DF71402B342F6900F29026 /* autofill-extension */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E1DF71412B342F6900F29026 /* CredentialProviderViewController.swift */,
|
||||
E1DF71432B342F6900F29026 /* CredentialProviderViewController.xib */,
|
||||
E1DF71462B342F6900F29026 /* Info.plist */,
|
||||
E1DF71472B342F6900F29026 /* autofill_extension.entitlements */,
|
||||
);
|
||||
path = "autofill-extension";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
E1DF713B2B342F6900F29026 /* autofill-extension */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = E1DF714E2B342F6900F29026 /* Build configuration list for PBXNativeTarget "autofill-extension" */;
|
||||
buildPhases = (
|
||||
E1DF71382B342F6900F29026 /* Sources */,
|
||||
E1DF71392B342F6900F29026 /* Frameworks */,
|
||||
E1DF713A2B342F6900F29026 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = "autofill-extension";
|
||||
productName = "autofill-extension";
|
||||
productReference = E1DF713C2B342F6900F29026 /* autofill-extension.appex */;
|
||||
productType = "com.apple.product-type.app-extension";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
E1DF711E2B342E2800F29026 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
BuildIndependentTargetsInParallel = 1;
|
||||
LastSwiftUpdateCheck = 1510;
|
||||
LastUpgradeCheck = 1510;
|
||||
TargetAttributes = {
|
||||
E1DF713B2B342F6900F29026 = {
|
||||
CreatedOnToolsVersion = 15.1;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = E1DF71212B342E2800F29026 /* Build configuration list for PBXProject "desktop" */;
|
||||
compatibilityVersion = "Xcode 14.0";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = E1DF711D2B342E2800F29026;
|
||||
productRefGroup = E1DF71272B342E2800F29026 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
E1DF713B2B342F6900F29026 /* autofill-extension */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
E1DF713A2B342F6900F29026 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
E1DF71452B342F6900F29026 /* CredentialProviderViewController.xib in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
E1DF71382B342F6900F29026 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
E1DF71422B342F6900F29026 /* CredentialProviderViewController.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
E1DF71432B342F6900F29026 /* CredentialProviderViewController.xib */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
E1DF71442B342F6900F29026 /* Base */,
|
||||
);
|
||||
name = CredentialProviderViewController.xib;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
E1DF71332B342E2900F29026 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "autofill-extension/autofill_extension.entitlements";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 14.2;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = macosx;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
E1DF71342B342E2900F29026 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "autofill-extension/autofill_extension.entitlements";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 14.2;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
SDKROOT = macosx;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
E1DF714C2B342F6900F29026 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_ENTITLEMENTS = "autofill-extension/autofill_extension.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Mac Developer";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=macosx*]" = LTZ2PFU5D6;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = "autofill-extension/Info.plist";
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Bitwarden;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
"@executable_path/../../../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.bitwarden.desktop.autofill-extension";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "Bitwarden Desktop Autofill Development 2024";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
E1DF714D2B342F6900F29026 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_ENTITLEMENTS = "autofill-extension/autofill_extension.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Mac Developer";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=macosx*]" = LTZ2PFU5D6;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = "autofill-extension/Info.plist";
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Bitwarden;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
"@executable_path/../../../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.bitwarden.desktop.autofill-extension";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "Bitwarden Desktop Autofill Development 2024";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
E1DF71212B342E2800F29026 /* Build configuration list for PBXProject "desktop" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
E1DF71332B342E2900F29026 /* Debug */,
|
||||
E1DF71342B342E2900F29026 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
E1DF714E2B342F6900F29026 /* Build configuration list for PBXNativeTarget "autofill-extension" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
E1DF714C2B342F6900F29026 /* Debug */,
|
||||
E1DF714D2B342F6900F29026 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = E1DF711E2B342E2800F29026 /* Project object */;
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
11
apps/desktop/macos/production.xcconfig
Normal file
11
apps/desktop/macos/production.xcconfig
Normal file
@ -0,0 +1,11 @@
|
||||
//
|
||||
// Production.xcconfig
|
||||
// desktop
|
||||
//
|
||||
// Created by Vince Grassia on 7/25/24.
|
||||
//
|
||||
|
||||
// Configuration settings file format documentation can be found at:
|
||||
// https://help.apple.com/xcode/#/dev745c5c974
|
||||
CODE_SIGN_IDENTITY[sdk=macosx*] = 3rd Party Mac Developer Application
|
||||
PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*] = Bitwarden Desktop Autofill App Store 2024
|
@ -23,6 +23,7 @@
|
||||
"build:dev": "concurrently -n Main,Rend -c yellow,cyan \"npm run build:main:dev\" \"npm run build:renderer:dev\"",
|
||||
"build:preload": "cross-env NODE_ENV=production webpack --config webpack.preload.js",
|
||||
"build:preload:watch": "cross-env NODE_ENV=production webpack --config webpack.preload.js --watch",
|
||||
"build:macos-extension": "node scripts/build-macos-extension.js",
|
||||
"build:main": "cross-env NODE_ENV=production webpack --config webpack.main.js",
|
||||
"build:main:dev": "npm run build-native && cross-env NODE_ENV=development webpack --config webpack.main.js",
|
||||
"build:main:watch": "npm run build-native && cross-env NODE_ENV=development webpack --config webpack.main.js --watch",
|
||||
@ -33,11 +34,13 @@
|
||||
"electron:ignore": "node ./scripts/start.js --ignore-certificate-errors",
|
||||
"clean:dist": "rimraf ./dist",
|
||||
"pack:dir": "npm run clean:dist && electron-builder --dir -p never",
|
||||
"pack:lin:flatpak": "npm run clean:dist && electron-builder --dir -p never && flatpak-builder --repo=build/.repo build/.flatpak ./resources/com.bitwarden.desktop.devel.yaml --install-deps-from=flathub --force-clean && flatpak build-bundle ./build/.repo/ ./dist/com.bitwarden.desktop.flatpak com.bitwarden.desktop",
|
||||
"pack:lin": "npm run clean:dist && electron-builder --linux --x64 -p never",
|
||||
"pack:mac": "npm run clean:dist && electron-builder --mac --universal -p never",
|
||||
"pack:mac:arm64": "npm run clean:dist && electron-builder --mac --arm64 -p never",
|
||||
"pack:mac:mas": "npm run clean:dist && electron-builder --mac mas --universal -p never",
|
||||
"pack:mac:masdev": "npm run clean:dist && electron-builder --mac mas-dev --universal -p never",
|
||||
"pack:mac:masdev:with-extension": "npm run clean:dist && npm run build:macos-extension && electron-builder --mac mas-dev --universal -p never",
|
||||
"pack:win": "npm run clean:dist && electron-builder --win --x64 --arm64 --ia32 -p never -c.win.certificateSubjectName=\"8bit Solutions LLC\"",
|
||||
"pack:win:ci": "npm run clean:dist && electron-builder --win --x64 --arm64 --ia32 -p never",
|
||||
"dist:dir": "npm run build && npm run pack:dir",
|
||||
|
43
apps/desktop/resources/com.bitwarden.desktop.devel.yaml
Normal file
43
apps/desktop/resources/com.bitwarden.desktop.devel.yaml
Normal file
@ -0,0 +1,43 @@
|
||||
app-id: com.bitwarden.desktop
|
||||
runtime: org.freedesktop.Platform
|
||||
runtime-version: "24.08"
|
||||
sdk: org.freedesktop.Sdk
|
||||
base: org.electronjs.Electron2.BaseApp
|
||||
base-version: "24.08"
|
||||
command: bitwarden.sh
|
||||
finish-args:
|
||||
- --share=ipc
|
||||
- --share=network
|
||||
- --socket=wayland
|
||||
- --socket=x11
|
||||
- --device=dri
|
||||
- --env=XDG_CURRENT_DESKTOP=Unity
|
||||
- --env=XCURSOR_PATH=/run/host/user-share/icons:/run/host/share/icons
|
||||
- --talk-name=org.kde.StatusNotifierWatcher
|
||||
- --talk-name=org.freedesktop.Notifications
|
||||
- --talk-name=org.freedesktop.secrets
|
||||
- --talk-name=com.canonical.AppMenu.Registrar
|
||||
- --system-talk-name=org.freedesktop.PolicyKit1
|
||||
# Lock on lockscreen
|
||||
- --talk-name=org.gnome.ScreenSaver
|
||||
- --talk-name=org.freedesktop.ScreenSaver
|
||||
- --system-talk-name=org.freedesktop.login1
|
||||
- --filesystem=xdg-download
|
||||
modules:
|
||||
- name: bitwarden-desktop
|
||||
buildsystem: simple
|
||||
build-commands:
|
||||
- mkdir -p /app/bin
|
||||
- mkdir -p /app/bin/Bitwarden/
|
||||
- cp -r ./* /app/bin/
|
||||
- install bitwarden.sh /app/bin/bitwarden.sh
|
||||
sources:
|
||||
- type: dir
|
||||
path: ../dist/linux-unpacked
|
||||
- type: script
|
||||
dest-filename: bitwarden.sh
|
||||
commands:
|
||||
- ulimit -c 0
|
||||
- export TMPDIR="$XDG_RUNTIME_DIR/app/$FLATPAK_ID"
|
||||
- exec zypak-wrapper /app/bin/bitwarden-app --ozone-platform-hint=auto
|
||||
--enable-features=WaylandWindowDecorations "$@"
|
@ -8,5 +8,7 @@
|
||||
<true/>
|
||||
<key>com.apple.security.cs.disable-library-validation</key>
|
||||
<true/>
|
||||
<key>com.apple.developer.authentication-services.autofill-credential-provider</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
@ -10,5 +10,7 @@
|
||||
<true/>
|
||||
<key>com.apple.security.cs.disable-library-validation</key>
|
||||
<true/>
|
||||
<key>com.apple.developer.authentication-services.autofill-credential-provider</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
@ -16,6 +16,8 @@
|
||||
<true/>
|
||||
<key>com.apple.security.files.user-selected.read-write</key>
|
||||
<true/>
|
||||
<key>com.apple.developer.authentication-services.autofill-credential-provider</key>
|
||||
<true/>
|
||||
<key>com.apple.security.temporary-exception.files.home-relative-path.read-write</key>
|
||||
<array>
|
||||
<string>/Library/Application Support/Mozilla/NativeMessagingHosts/</string>
|
||||
|
@ -15,36 +15,62 @@ async function run(context) {
|
||||
const appName = context.packager.appInfo.productFilename;
|
||||
const appPath = `${context.appOutDir}/${appName}.app`;
|
||||
const macBuild = context.electronPlatformName === "darwin";
|
||||
const copyPlugIn = ["darwin", "mas"].includes(context.electronPlatformName);
|
||||
const copySafariExtension = ["darwin", "mas"].includes(context.electronPlatformName);
|
||||
const copyAutofillExtension = ["mas"].includes(context.electronPlatformName);
|
||||
|
||||
if (copyPlugIn) {
|
||||
let shouldResign = false;
|
||||
|
||||
// cannot use extraFiles because it modifies the extensions .plist and makes it invalid
|
||||
if (copyAutofillExtension) {
|
||||
console.log("### Copying autofill extension");
|
||||
const extensionPath = path.join(__dirname, "../macos/dist/autofill-extension.appex");
|
||||
if (!fse.existsSync(extensionPath)) {
|
||||
console.log("### Autofill extension not found - skipping");
|
||||
} else {
|
||||
if (!fse.existsSync(path.join(appPath, "Contents/PlugIns"))) {
|
||||
fse.mkdirSync(path.join(appPath, "Contents/PlugIns"));
|
||||
}
|
||||
fse.copySync(extensionPath, path.join(appPath, "Contents/PlugIns/autofill-extension.appex"));
|
||||
shouldResign = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (copySafariExtension) {
|
||||
console.log("### Copying safari extension");
|
||||
// Copy Safari plugin to work-around https://github.com/electron-userland/electron-builder/issues/5552
|
||||
const plugIn = path.join(__dirname, "../PlugIns");
|
||||
if (fse.existsSync(plugIn)) {
|
||||
fse.mkdirSync(path.join(appPath, "Contents/PlugIns"));
|
||||
if (!fse.existsSync(plugIn)) {
|
||||
console.log("### Safari extension not found - skipping");
|
||||
} else {
|
||||
if (!fse.existsSync(path.join(appPath, "Contents/PlugIns"))) {
|
||||
fse.mkdirSync(path.join(appPath, "Contents/PlugIns"));
|
||||
}
|
||||
fse.copySync(
|
||||
path.join(plugIn, "safari.appex"),
|
||||
path.join(appPath, "Contents/PlugIns/safari.appex"),
|
||||
);
|
||||
shouldResign = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Resign to sign safari extension
|
||||
if (context.electronPlatformName === "mas") {
|
||||
const masBuildOptions = deepAssign(
|
||||
{},
|
||||
context.packager.platformSpecificBuildOptions,
|
||||
context.packager.config.mas,
|
||||
);
|
||||
if (context.targets.some((e) => e.name === "mas-dev")) {
|
||||
deepAssign(masBuildOptions, {
|
||||
type: "development",
|
||||
});
|
||||
}
|
||||
if (context.packager.packagerOptions.prepackaged == null) {
|
||||
await context.packager.sign(appPath, context.appOutDir, masBuildOptions, context.arch);
|
||||
}
|
||||
} else {
|
||||
await context.packager.signApp(context, true);
|
||||
if (shouldResign) {
|
||||
// Resign to sign safari extension
|
||||
if (context.electronPlatformName === "mas") {
|
||||
const masBuildOptions = deepAssign(
|
||||
{},
|
||||
context.packager.platformSpecificBuildOptions,
|
||||
context.packager.config.mas,
|
||||
);
|
||||
if (context.targets.some((e) => e.name === "mas-dev")) {
|
||||
deepAssign(masBuildOptions, {
|
||||
type: "development",
|
||||
});
|
||||
}
|
||||
if (context.packager.packagerOptions.prepackaged == null) {
|
||||
await context.packager.sign(appPath, context.appOutDir, masBuildOptions, context.arch);
|
||||
}
|
||||
} else {
|
||||
await context.packager.signApp(context, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
62
apps/desktop/scripts/build-macos-extension.js
Normal file
62
apps/desktop/scripts/build-macos-extension.js
Normal file
@ -0,0 +1,62 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires, no-console */
|
||||
const child = require("child_process");
|
||||
const { exit } = require("process");
|
||||
|
||||
const fse = require("fs-extra");
|
||||
|
||||
const paths = {
|
||||
macosBuild: "./macos/build",
|
||||
extensionBuild: "./macos/build/Release/autofill-extension.appex",
|
||||
extensionDistDir: "./macos/dist",
|
||||
extensionDist: "./macos/dist/autofill-extension.appex",
|
||||
macOsProject: "./macos/desktop.xcodeproj",
|
||||
macOsConfig: "./macos/production.xcconfig",
|
||||
};
|
||||
|
||||
async function buildMacOs() {
|
||||
if (fse.existsSync(paths.macosBuild)) {
|
||||
fse.removeSync(paths.macosBuild);
|
||||
}
|
||||
|
||||
if (fse.existsSync(paths.extensionDistDir)) {
|
||||
fse.removeSync(paths.extensionDistDir);
|
||||
}
|
||||
|
||||
const proc = child.spawn("xcodebuild", [
|
||||
"-project",
|
||||
paths.macOsProject,
|
||||
"-alltargets",
|
||||
"-configuration",
|
||||
"Release",
|
||||
"-xcconfig",
|
||||
paths.macOsConfig,
|
||||
]);
|
||||
stdOutProc(proc);
|
||||
await new Promise((resolve, reject) =>
|
||||
proc.on("close", (code) => {
|
||||
if (code > 0) {
|
||||
console.error("xcodebuild failed with code", code);
|
||||
return reject(new Error(`xcodebuild failed with code ${code}`));
|
||||
}
|
||||
console.log("xcodebuild success");
|
||||
resolve();
|
||||
}),
|
||||
);
|
||||
|
||||
fse.mkdirSync(paths.extensionDistDir);
|
||||
fse.copySync(paths.extensionBuild, paths.extensionDist);
|
||||
// Delete the build dir, otherwise MacOS will load the extension from there instead of the Bitwarden.app bundle
|
||||
fse.removeSync(paths.macosBuild);
|
||||
}
|
||||
|
||||
function stdOutProc(proc) {
|
||||
proc.stdout.on("data", (data) => console.log(data.toString()));
|
||||
proc.stderr.on("data", (data) => console.error(data.toString()));
|
||||
}
|
||||
|
||||
buildMacOs()
|
||||
.then(() => console.log("macOS build complete"))
|
||||
.catch((err) => {
|
||||
console.error("macOS build failed", err);
|
||||
exit(-1);
|
||||
});
|
@ -55,10 +55,6 @@ export class OrganizationLayoutComponent implements OnInit, OnDestroy {
|
||||
|
||||
private _destroy = new Subject<void>();
|
||||
|
||||
protected consolidatedBillingEnabled$ = this.configService.getFeatureFlag$(
|
||||
FeatureFlag.EnableConsolidatedBilling,
|
||||
);
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private organizationService: OrganizationService,
|
||||
@ -101,14 +97,9 @@ export class OrganizationLayoutComponent implements OnInit, OnDestroy {
|
||||
switchMap((organization) => this.providerService.get$(organization.providerId)),
|
||||
);
|
||||
|
||||
this.organizationIsUnmanaged$ = combineLatest([
|
||||
this.consolidatedBillingEnabled$,
|
||||
this.organization$,
|
||||
provider$,
|
||||
]).pipe(
|
||||
this.organizationIsUnmanaged$ = combineLatest([this.organization$, provider$]).pipe(
|
||||
map(
|
||||
([consolidatedBillingEnabled, organization, provider]) =>
|
||||
!consolidatedBillingEnabled ||
|
||||
([organization, provider]) =>
|
||||
!organization.hasProvider ||
|
||||
!provider ||
|
||||
provider.providerStatus !== ProviderStatusType.Billable,
|
||||
|
@ -4,22 +4,11 @@ import { ActivatedRouteSnapshot, CanActivateFn } from "@angular/router";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
|
||||
import { ProviderStatusType } from "@bitwarden/common/admin-console/enums";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
|
||||
export const organizationIsUnmanaged: CanActivateFn = async (route: ActivatedRouteSnapshot) => {
|
||||
const configService = inject(ConfigService);
|
||||
const organizationService = inject(OrganizationService);
|
||||
const providerService = inject(ProviderService);
|
||||
|
||||
const consolidatedBillingEnabled = await configService.getFeatureFlag(
|
||||
FeatureFlag.EnableConsolidatedBilling,
|
||||
);
|
||||
|
||||
if (!consolidatedBillingEnabled) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const organization = await organizationService.get(route.params.organizationId);
|
||||
|
||||
if (!organization.hasProvider) {
|
||||
|
@ -60,10 +60,6 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
|
||||
protected readonly subscriptionHiddenIcon = SubscriptionHiddenIcon;
|
||||
protected readonly teamsStarter = ProductTierType.TeamsStarter;
|
||||
|
||||
protected enableConsolidatedBilling$ = this.configService.getFeatureFlag$(
|
||||
FeatureFlag.EnableConsolidatedBilling,
|
||||
);
|
||||
|
||||
protected deprecateStripeSourcesAPI$ = this.configService.getFeatureFlag$(
|
||||
FeatureFlag.AC2476_DeprecateStripeSourcesAPI,
|
||||
);
|
||||
@ -120,8 +116,6 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
|
||||
this.locale = await firstValueFrom(this.i18nService.locale$);
|
||||
this.userOrg = await this.organizationService.get(this.organizationId);
|
||||
|
||||
const consolidatedBillingEnabled = await firstValueFrom(this.enableConsolidatedBilling$);
|
||||
|
||||
const isIndependentOrganizationOwner = !this.userOrg.hasProvider && this.userOrg.isOwner;
|
||||
const isResoldOrganizationOwner = this.userOrg.hasReseller && this.userOrg.isOwner;
|
||||
const isMSPUser = this.userOrg.hasProvider && this.userOrg.isProviderUser;
|
||||
@ -131,7 +125,7 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
|
||||
);
|
||||
|
||||
this.organizationIsManagedByConsolidatedBillingMSP =
|
||||
consolidatedBillingEnabled && this.userOrg.hasProvider && metadata.isManaged;
|
||||
this.userOrg.hasProvider && metadata.isManaged;
|
||||
|
||||
this.showSubscription =
|
||||
isIndependentOrganizationOwner ||
|
||||
|
@ -17,7 +17,7 @@
|
||||
</a>
|
||||
</bit-menu>
|
||||
<div>
|
||||
{{ "server" | i18n }}:
|
||||
{{ "accessing" | i18n }}:
|
||||
<a [routerLink]="[]" [bitMenuTriggerFor]="environmentOptions">
|
||||
<b>{{ currentRegion?.domain }}</b
|
||||
><i class="bwi bwi-fw bwi-sm bwi-angle-down" aria-hidden="true"></i>
|
||||
|
@ -4,7 +4,11 @@ import { mock, MockProxy } from "jest-mock-extended";
|
||||
import { of } from "rxjs";
|
||||
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { PasswordHealthService } from "@bitwarden/bit-common/tools/reports/risk-insights";
|
||||
import {
|
||||
MemberCipherDetailsApiService,
|
||||
PasswordHealthService,
|
||||
} from "@bitwarden/bit-common/tools/reports/risk-insights";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
@ -46,6 +50,14 @@ describe("PasswordHealthMembersUriComponent", () => {
|
||||
url: of([]),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: MemberCipherDetailsApiService,
|
||||
useValue: mock<MemberCipherDetailsApiService>(),
|
||||
},
|
||||
{
|
||||
provide: ApiService,
|
||||
useValue: mock<ApiService>(),
|
||||
},
|
||||
],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
@ -6,7 +6,10 @@ import { map } from "rxjs";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { PasswordHealthService } from "@bitwarden/bit-common/tools/reports/risk-insights";
|
||||
import {
|
||||
MemberCipherDetailsApiService,
|
||||
PasswordHealthService,
|
||||
} from "@bitwarden/bit-common/tools/reports/risk-insights";
|
||||
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
@ -25,8 +28,6 @@ import {
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { HeaderModule } from "../../layouts/header/header.module";
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { OrganizationBadgeModule } from "../../vault/individual-vault/organization-badge/organization-badge.module";
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { PipesModule } from "../../vault/individual-vault/pipes/pipes.module";
|
||||
|
||||
@Component({
|
||||
@ -35,7 +36,6 @@ import { PipesModule } from "../../vault/individual-vault/pipes/pipes.module";
|
||||
templateUrl: "password-health-members-uri.component.html",
|
||||
imports: [
|
||||
BadgeModule,
|
||||
OrganizationBadgeModule,
|
||||
CommonModule,
|
||||
ContainerComponent,
|
||||
PipesModule,
|
||||
@ -43,7 +43,7 @@ import { PipesModule } from "../../vault/individual-vault/pipes/pipes.module";
|
||||
HeaderModule,
|
||||
TableModule,
|
||||
],
|
||||
providers: [PasswordHealthService],
|
||||
providers: [PasswordHealthService, MemberCipherDetailsApiService],
|
||||
})
|
||||
export class PasswordHealthMembersURIComponent implements OnInit {
|
||||
passwordStrengthMap = new Map<string, [string, BadgeVariant]>();
|
||||
@ -74,6 +74,7 @@ export class PasswordHealthMembersURIComponent implements OnInit {
|
||||
protected auditService: AuditService,
|
||||
protected i18nService: I18nService,
|
||||
protected activatedRoute: ActivatedRoute,
|
||||
protected memberCipherDetailsApiService: MemberCipherDetailsApiService,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
@ -93,6 +94,7 @@ export class PasswordHealthMembersURIComponent implements OnInit {
|
||||
this.passwordStrengthService,
|
||||
this.auditService,
|
||||
this.cipherService,
|
||||
this.memberCipherDetailsApiService,
|
||||
organizationId,
|
||||
);
|
||||
|
||||
|
@ -5,7 +5,10 @@ import { ActivatedRoute } from "@angular/router";
|
||||
import { debounceTime, map } from "rxjs";
|
||||
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { PasswordHealthService } from "@bitwarden/bit-common/tools/reports/risk-insights";
|
||||
import {
|
||||
MemberCipherDetailsApiService,
|
||||
PasswordHealthService,
|
||||
} from "@bitwarden/bit-common/tools/reports/risk-insights";
|
||||
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
|
||||
@ -18,12 +21,10 @@ import {
|
||||
TableModule,
|
||||
ToastService,
|
||||
} from "@bitwarden/components";
|
||||
import { CardComponent } from "@bitwarden/tools-card";
|
||||
|
||||
import { HeaderModule } from "../../layouts/header/header.module";
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { SharedModule } from "../../shared";
|
||||
import { OrganizationBadgeModule } from "../../vault/individual-vault/organization-badge/organization-badge.module";
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { PipesModule } from "../../vault/individual-vault/pipes/pipes.module";
|
||||
|
||||
@ -31,17 +32,8 @@ import { PipesModule } from "../../vault/individual-vault/pipes/pipes.module";
|
||||
standalone: true,
|
||||
selector: "tools-password-health-members",
|
||||
templateUrl: "password-health-members.component.html",
|
||||
imports: [
|
||||
CardComponent,
|
||||
OrganizationBadgeModule,
|
||||
PipesModule,
|
||||
HeaderModule,
|
||||
SearchModule,
|
||||
FormsModule,
|
||||
SharedModule,
|
||||
TableModule,
|
||||
],
|
||||
providers: [PasswordHealthService],
|
||||
imports: [PipesModule, HeaderModule, SearchModule, FormsModule, SharedModule, TableModule],
|
||||
providers: [PasswordHealthService, MemberCipherDetailsApiService],
|
||||
})
|
||||
export class PasswordHealthMembersComponent implements OnInit {
|
||||
passwordStrengthMap = new Map<string, [string, BadgeVariant]>();
|
||||
@ -69,6 +61,7 @@ export class PasswordHealthMembersComponent implements OnInit {
|
||||
protected i18nService: I18nService,
|
||||
protected activatedRoute: ActivatedRoute,
|
||||
protected toastService: ToastService,
|
||||
protected memberCipherDetailsApiService: MemberCipherDetailsApiService,
|
||||
) {
|
||||
this.searchControl.valueChanges
|
||||
.pipe(debounceTime(200), takeUntilDestroyed())
|
||||
@ -92,6 +85,7 @@ export class PasswordHealthMembersComponent implements OnInit {
|
||||
this.passwordStrengthService,
|
||||
this.auditService,
|
||||
this.cipherService,
|
||||
this.memberCipherDetailsApiService,
|
||||
organizationId,
|
||||
);
|
||||
|
||||
|
@ -4,7 +4,11 @@ import { mock } from "jest-mock-extended";
|
||||
import { of } from "rxjs";
|
||||
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { PasswordHealthService } from "@bitwarden/bit-common/tools/reports/risk-insights";
|
||||
import {
|
||||
MemberCipherDetailsApiService,
|
||||
PasswordHealthService,
|
||||
} from "@bitwarden/bit-common/tools/reports/risk-insights";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
|
||||
@ -30,6 +34,8 @@ describe("PasswordHealthComponent", () => {
|
||||
{ provide: CipherService, useValue: mock<CipherService>() },
|
||||
{ provide: I18nService, useValue: mock<I18nService>() },
|
||||
{ provide: AuditService, useValue: mock<AuditService>() },
|
||||
{ provide: ApiService, useValue: mock<ApiService>() },
|
||||
{ provide: MemberCipherDetailsApiService, useValue: mock<MemberCipherDetailsApiService>() },
|
||||
{
|
||||
provide: PasswordStrengthServiceAbstraction,
|
||||
useValue: mock<PasswordStrengthServiceAbstraction>(),
|
||||
|
@ -6,7 +6,10 @@ import { map } from "rxjs";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { PasswordHealthService } from "@bitwarden/bit-common/tools/reports/risk-insights";
|
||||
import {
|
||||
MemberCipherDetailsApiService,
|
||||
PasswordHealthService,
|
||||
} from "@bitwarden/bit-common/tools/reports/risk-insights";
|
||||
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
|
||||
@ -41,7 +44,7 @@ import { PipesModule } from "../../vault/individual-vault/pipes/pipes.module";
|
||||
HeaderModule,
|
||||
TableModule,
|
||||
],
|
||||
providers: [PasswordHealthService],
|
||||
providers: [PasswordHealthService, MemberCipherDetailsApiService],
|
||||
})
|
||||
export class PasswordHealthComponent implements OnInit {
|
||||
passwordStrengthMap = new Map<string, [string, BadgeVariant]>();
|
||||
@ -62,6 +65,7 @@ export class PasswordHealthComponent implements OnInit {
|
||||
protected auditService: AuditService,
|
||||
protected i18nService: I18nService,
|
||||
protected activatedRoute: ActivatedRoute,
|
||||
protected memberCipherDetailsApiService: MemberCipherDetailsApiService,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
@ -81,6 +85,7 @@ export class PasswordHealthComponent implements OnInit {
|
||||
this.passwordStrengthService,
|
||||
this.auditService,
|
||||
this.cipherService,
|
||||
this.memberCipherDetailsApiService,
|
||||
organizationId,
|
||||
);
|
||||
|
||||
|
@ -4,74 +4,72 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
|
||||
import { MemberCipherDetailsApiService } from "./member-cipher-details-api.service";
|
||||
|
||||
const mockMemberCipherDetails: any = {
|
||||
data: [
|
||||
{
|
||||
userName: "David Brent",
|
||||
email: "david.brent@wernhamhogg.uk",
|
||||
usesKeyConnector: true,
|
||||
cipherIds: [
|
||||
"cbea34a8-bde4-46ad-9d19-b05001228ab1",
|
||||
"cbea34a8-bde4-46ad-9d19-b05001228ab2",
|
||||
"cbea34a8-bde4-46ad-9d19-b05001228xy4",
|
||||
"cbea34a8-bde4-46ad-9d19-b05001227nm5",
|
||||
],
|
||||
},
|
||||
{
|
||||
userName: "Tim Canterbury",
|
||||
email: "tim.canterbury@wernhamhogg.uk",
|
||||
usesKeyConnector: false,
|
||||
cipherIds: [
|
||||
"cbea34a8-bde4-46ad-9d19-b05001228ab2",
|
||||
"cbea34a8-bde4-46ad-9d19-b05001228cd3",
|
||||
"cbea34a8-bde4-46ad-9d19-b05001228xy4",
|
||||
"cbea34a8-bde4-46ad-9d19-b05001227nm5",
|
||||
],
|
||||
},
|
||||
{
|
||||
userName: "Gareth Keenan",
|
||||
email: "gareth.keenan@wernhamhogg.uk",
|
||||
usesKeyConnector: true,
|
||||
cipherIds: [
|
||||
"cbea34a8-bde4-46ad-9d19-b05001228cd3",
|
||||
"cbea34a8-bde4-46ad-9d19-b05001228xy4",
|
||||
"cbea34a8-bde4-46ad-9d19-b05001227nm5",
|
||||
"cbea34a8-bde4-46ad-9d19-b05001227nm7",
|
||||
],
|
||||
},
|
||||
{
|
||||
userName: "Dawn Tinsley",
|
||||
email: "dawn.tinsley@wernhamhogg.uk",
|
||||
usesKeyConnector: true,
|
||||
cipherIds: [
|
||||
"cbea34a8-bde4-46ad-9d19-b05001228ab2",
|
||||
"cbea34a8-bde4-46ad-9d19-b05001228cd3",
|
||||
"cbea34a8-bde4-46ad-9d19-b05001228xy4",
|
||||
],
|
||||
},
|
||||
{
|
||||
userName: "Keith Bishop",
|
||||
email: "keith.bishop@wernhamhogg.uk",
|
||||
usesKeyConnector: false,
|
||||
cipherIds: [
|
||||
"cbea34a8-bde4-46ad-9d19-b05001228ab1",
|
||||
"cbea34a8-bde4-46ad-9d19-b05001228cd3",
|
||||
"cbea34a8-bde4-46ad-9d19-b05001228xy4",
|
||||
"cbea34a8-bde4-46ad-9d19-b05001227nm5",
|
||||
],
|
||||
},
|
||||
{
|
||||
userName: "Chris Finch",
|
||||
email: "chris.finch@wernhamhogg.uk",
|
||||
usesKeyConnector: true,
|
||||
cipherIds: [
|
||||
"cbea34a8-bde4-46ad-9d19-b05001228ab2",
|
||||
"cbea34a8-bde4-46ad-9d19-b05001228cd3",
|
||||
"cbea34a8-bde4-46ad-9d19-b05001228xy4",
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
export const mockMemberCipherDetails: any = [
|
||||
{
|
||||
userName: "David Brent",
|
||||
email: "david.brent@wernhamhogg.uk",
|
||||
usesKeyConnector: true,
|
||||
cipherIds: [
|
||||
"cbea34a8-bde4-46ad-9d19-b05001228ab1",
|
||||
"cbea34a8-bde4-46ad-9d19-b05001228ab2",
|
||||
"cbea34a8-bde4-46ad-9d19-b05001228xy4",
|
||||
"cbea34a8-bde4-46ad-9d19-b05001227nm5",
|
||||
],
|
||||
},
|
||||
{
|
||||
userName: "Tim Canterbury",
|
||||
email: "tim.canterbury@wernhamhogg.uk",
|
||||
usesKeyConnector: false,
|
||||
cipherIds: [
|
||||
"cbea34a8-bde4-46ad-9d19-b05001228ab2",
|
||||
"cbea34a8-bde4-46ad-9d19-b05001228cd3",
|
||||
"cbea34a8-bde4-46ad-9d19-b05001228xy4",
|
||||
"cbea34a8-bde4-46ad-9d19-b05001227nm5",
|
||||
],
|
||||
},
|
||||
{
|
||||
userName: "Gareth Keenan",
|
||||
email: "gareth.keenan@wernhamhogg.uk",
|
||||
usesKeyConnector: true,
|
||||
cipherIds: [
|
||||
"cbea34a8-bde4-46ad-9d19-b05001228cd3",
|
||||
"cbea34a8-bde4-46ad-9d19-b05001228xy4",
|
||||
"cbea34a8-bde4-46ad-9d19-b05001227nm5",
|
||||
"cbea34a8-bde4-46ad-9d19-b05001227nm7",
|
||||
],
|
||||
},
|
||||
{
|
||||
userName: "Dawn Tinsley",
|
||||
email: "dawn.tinsley@wernhamhogg.uk",
|
||||
usesKeyConnector: true,
|
||||
cipherIds: [
|
||||
"cbea34a8-bde4-46ad-9d19-b05001228ab2",
|
||||
"cbea34a8-bde4-46ad-9d19-b05001228cd3",
|
||||
"cbea34a8-bde4-46ad-9d19-b05001228xy4",
|
||||
],
|
||||
},
|
||||
{
|
||||
userName: "Keith Bishop",
|
||||
email: "keith.bishop@wernhamhogg.uk",
|
||||
usesKeyConnector: false,
|
||||
cipherIds: [
|
||||
"cbea34a8-bde4-46ad-9d19-b05001228ab1",
|
||||
"cbea34a8-bde4-46ad-9d19-b05001228cd3",
|
||||
"cbea34a8-bde4-46ad-9d19-b05001228xy4",
|
||||
"cbea34a8-bde4-46ad-9d19-b05001227nm5",
|
||||
],
|
||||
},
|
||||
{
|
||||
userName: "Chris Finch",
|
||||
email: "chris.finch@wernhamhogg.uk",
|
||||
usesKeyConnector: true,
|
||||
cipherIds: [
|
||||
"cbea34a8-bde4-46ad-9d19-b05001228ab2",
|
||||
"cbea34a8-bde4-46ad-9d19-b05001228cd3",
|
||||
"cbea34a8-bde4-46ad-9d19-b05001228xy4",
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
describe("Member Cipher Details API Service", () => {
|
||||
let memberCipherDetailsApiService: MemberCipherDetailsApiService;
|
||||
|
@ -1,8 +1,10 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { ListResponse } from "@bitwarden/common/models/response/list.response";
|
||||
|
||||
import { MemberCipherDetailsResponse } from "../response/member-cipher-details.response";
|
||||
|
||||
@Injectable()
|
||||
export class MemberCipherDetailsApiService {
|
||||
constructor(private apiService: ApiService) {}
|
||||
|
||||
@ -21,7 +23,6 @@ export class MemberCipherDetailsApiService {
|
||||
true,
|
||||
);
|
||||
|
||||
const listResponse = new ListResponse(response, MemberCipherDetailsResponse);
|
||||
return listResponse.data.map((r) => new MemberCipherDetailsResponse(r));
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
@ -3,15 +3,17 @@ import { TestBed } from "@angular/core/testing";
|
||||
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
|
||||
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { CipherData } from "@bitwarden/common/vault/models/data/cipher.data";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
|
||||
import { mockCiphers } from "./ciphers.mock";
|
||||
import { MemberCipherDetailsApiService } from "./member-cipher-details-api.service";
|
||||
import { mockMemberCipherDetails } from "./member-cipher-details-api.service.spec";
|
||||
import { PasswordHealthService } from "./password-health.service";
|
||||
|
||||
describe("PasswordHealthService", () => {
|
||||
let service: PasswordHealthService;
|
||||
let cipherService: CipherService;
|
||||
let memberCipherDetailsApiService: MemberCipherDetailsApiService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
@ -35,7 +37,13 @@ describe("PasswordHealthService", () => {
|
||||
{
|
||||
provide: CipherService,
|
||||
useValue: {
|
||||
getAllFromApiForOrganization: jest.fn().mockResolvedValue(CipherData),
|
||||
getAllFromApiForOrganization: jest.fn().mockResolvedValue(mockCiphers),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: MemberCipherDetailsApiService,
|
||||
useValue: {
|
||||
getMemberCipherDetails: jest.fn().mockResolvedValue(mockMemberCipherDetails),
|
||||
},
|
||||
},
|
||||
{ provide: "organizationId", useValue: "org1" },
|
||||
@ -44,6 +52,7 @@ describe("PasswordHealthService", () => {
|
||||
|
||||
service = TestBed.inject(PasswordHealthService);
|
||||
cipherService = TestBed.inject(CipherService);
|
||||
memberCipherDetailsApiService = TestBed.inject(MemberCipherDetailsApiService);
|
||||
});
|
||||
|
||||
it("should be created", () => {
|
||||
@ -68,6 +77,10 @@ describe("PasswordHealthService", () => {
|
||||
expect(cipherService.getAllFromApiForOrganization).toHaveBeenCalledWith("org1");
|
||||
});
|
||||
|
||||
it("should fetch member cipher details", () => {
|
||||
expect(memberCipherDetailsApiService.getMemberCipherDetails).toHaveBeenCalledWith("org1");
|
||||
});
|
||||
|
||||
it("should populate reportCiphers with ciphers that have issues", () => {
|
||||
expect(service.reportCiphers.length).toBeGreaterThan(0);
|
||||
});
|
||||
@ -99,12 +112,12 @@ describe("PasswordHealthService", () => {
|
||||
|
||||
it("should calculate total members per cipher", () => {
|
||||
expect(service.totalMembersMap.size).toBeGreaterThan(0);
|
||||
expect(service.totalMembersMap.get("cbea34a8-bde4-46ad-9d19-b05001228ab1")).toBe(3);
|
||||
expect(service.totalMembersMap.get("cbea34a8-bde4-46ad-9d19-b05001228ab2")).toBe(5);
|
||||
expect(service.totalMembersMap.get("cbea34a8-bde4-46ad-9d19-b05001228cd3")).toBe(6);
|
||||
expect(service.totalMembersMap.get("cbea34a8-bde4-46ad-9d19-b05001228ab1")).toBe(2);
|
||||
expect(service.totalMembersMap.get("cbea34a8-bde4-46ad-9d19-b05001228ab2")).toBe(4);
|
||||
expect(service.totalMembersMap.get("cbea34a8-bde4-46ad-9d19-b05001228cd3")).toBe(5);
|
||||
expect(service.totalMembersMap.get("cbea34a8-bde4-46ad-9d19-b05001227nm5")).toBe(4);
|
||||
expect(service.totalMembersMap.get("cbea34a8-bde4-46ad-9d19-b05001227nm7")).toBe(1);
|
||||
expect(service.totalMembersMap.get("cbea34a8-bde4-46ad-9d19-b05001228xy4")).toBe(7);
|
||||
expect(service.totalMembersMap.get("cbea34a8-bde4-46ad-9d19-b05001228xy4")).toBe(6);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -1,9 +1,5 @@
|
||||
import { Inject, Injectable } from "@angular/core";
|
||||
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { mockCiphers } from "@bitwarden/bit-common/tools/reports/risk-insights/services/ciphers.mock";
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { mockMemberCipherDetailsResponse } from "@bitwarden/bit-common/tools/reports/risk-insights/services/member-cipher-details-response.mock";
|
||||
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
|
||||
@ -12,6 +8,8 @@ import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { BadgeVariant } from "@bitwarden/components";
|
||||
|
||||
import { MemberCipherDetailsApiService } from "./member-cipher-details-api.service";
|
||||
|
||||
@Injectable()
|
||||
export class PasswordHealthService {
|
||||
reportCiphers: CipherView[] = [];
|
||||
@ -30,21 +28,23 @@ export class PasswordHealthService {
|
||||
private passwordStrengthService: PasswordStrengthServiceAbstraction,
|
||||
private auditService: AuditService,
|
||||
private cipherService: CipherService,
|
||||
private memberCipherDetailsApiService: MemberCipherDetailsApiService,
|
||||
@Inject("organizationId") private organizationId: string,
|
||||
) {}
|
||||
|
||||
async generateReport() {
|
||||
let allCiphers = await this.cipherService.getAllFromApiForOrganization(this.organizationId);
|
||||
// TODO remove when actual user member data is available
|
||||
allCiphers = mockCiphers;
|
||||
const allCiphers = await this.cipherService.getAllFromApiForOrganization(this.organizationId);
|
||||
allCiphers.forEach(async (cipher) => {
|
||||
this.findWeakPassword(cipher);
|
||||
this.findReusedPassword(cipher);
|
||||
await this.findExposedPassword(cipher);
|
||||
});
|
||||
|
||||
// TODO - fetch actual user member when data is available
|
||||
mockMemberCipherDetailsResponse.data.forEach((user) => {
|
||||
const memberCipherDetails = await this.memberCipherDetailsApiService.getMemberCipherDetails(
|
||||
this.organizationId,
|
||||
);
|
||||
|
||||
memberCipherDetails.forEach((user) => {
|
||||
user.cipherIds.forEach((cipherId: string) => {
|
||||
if (this.totalMembersMap.has(cipherId)) {
|
||||
this.totalMembersMap.set(cipherId, (this.totalMembersMap.get(cipherId) || 0) + 1);
|
||||
|
@ -8,11 +8,9 @@ import { SearchService } from "@bitwarden/common/abstractions/search.service";
|
||||
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
|
||||
import { ProviderUserType } from "@bitwarden/common/admin-console/enums";
|
||||
import { ProviderStatusType, ProviderUserType } from "@bitwarden/common/admin-console/enums";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { hasConsolidatedBilling } from "@bitwarden/common/billing/abstractions/provider-billing.service.abstraction";
|
||||
import { PlanType } from "@bitwarden/common/billing/enums";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
|
||||
import { DialogService, ToastService } from "@bitwarden/components";
|
||||
@ -46,7 +44,6 @@ export class ClientsComponent extends BaseClientsComponent implements OnInit, On
|
||||
private apiService: ApiService,
|
||||
private organizationService: OrganizationService,
|
||||
private organizationApiService: OrganizationApiServiceAbstraction,
|
||||
private configService: ConfigService,
|
||||
activatedRoute: ActivatedRoute,
|
||||
dialogService: DialogService,
|
||||
i18nService: I18nService,
|
||||
@ -72,9 +69,9 @@ export class ClientsComponent extends BaseClientsComponent implements OnInit, On
|
||||
switchMap((params) => {
|
||||
this.providerId = params.providerId;
|
||||
return this.providerService.get$(this.providerId).pipe(
|
||||
hasConsolidatedBilling(this.configService),
|
||||
map((hasConsolidatedBilling) => {
|
||||
if (hasConsolidatedBilling) {
|
||||
map((provider) => provider?.providerStatus === ProviderStatusType.Billable),
|
||||
map((isBillable) => {
|
||||
if (isBillable) {
|
||||
return from(
|
||||
this.router.navigate(["../manage-client-organizations"], {
|
||||
relativeTo: this.activatedRoute,
|
||||
|
@ -5,7 +5,7 @@
|
||||
<bit-nav-item
|
||||
icon="bwi-bank"
|
||||
[text]="'clients' | i18n"
|
||||
[route]="(hasConsolidatedBilling$ | async) ? 'manage-client-organizations' : 'clients'"
|
||||
[route]="(isBillable | async) ? 'manage-client-organizations' : 'clients'"
|
||||
></bit-nav-item>
|
||||
<bit-nav-group
|
||||
icon="bwi-sliders"
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, OnDestroy, OnInit } from "@angular/core";
|
||||
import { ActivatedRoute, RouterModule } from "@angular/router";
|
||||
import { switchMap, Observable, Subject, combineLatest, map } from "rxjs";
|
||||
import { combineLatest, map, Observable, Subject, switchMap } from "rxjs";
|
||||
import { takeUntil } from "rxjs/operators";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
|
||||
import { ProviderStatusType } from "@bitwarden/common/admin-console/enums";
|
||||
import { Provider } from "@bitwarden/common/admin-console/models/domain/provider";
|
||||
import { hasConsolidatedBilling } from "@bitwarden/common/billing/abstractions/provider-billing.service.abstraction";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { BannerModule, IconModule, LinkModule } from "@bitwarden/components";
|
||||
@ -36,7 +36,7 @@ export class ProvidersLayoutComponent implements OnInit, OnDestroy {
|
||||
private destroy$ = new Subject<void>();
|
||||
protected provider$: Observable<Provider>;
|
||||
|
||||
protected hasConsolidatedBilling$: Observable<boolean>;
|
||||
protected isBillable: Observable<boolean>;
|
||||
protected canAccessBilling$: Observable<boolean>;
|
||||
|
||||
protected showProviderClientVaultPrivacyWarningBanner$ = this.configService.getFeatureFlag$(
|
||||
@ -58,12 +58,12 @@ export class ProvidersLayoutComponent implements OnInit, OnDestroy {
|
||||
takeUntil(this.destroy$),
|
||||
);
|
||||
|
||||
this.hasConsolidatedBilling$ = this.provider$.pipe(
|
||||
hasConsolidatedBilling(this.configService),
|
||||
this.isBillable = this.provider$.pipe(
|
||||
map((provider) => provider?.providerStatus === ProviderStatusType.Billable),
|
||||
takeUntil(this.destroy$),
|
||||
);
|
||||
|
||||
this.canAccessBilling$ = combineLatest([this.hasConsolidatedBilling$, this.provider$]).pipe(
|
||||
this.canAccessBilling$ = combineLatest([this.isBillable, this.provider$]).pipe(
|
||||
map(
|
||||
([hasConsolidatedBilling, provider]) => hasConsolidatedBilling && provider.isProviderAdmin,
|
||||
),
|
||||
|
@ -24,13 +24,11 @@
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "billingEmail" | i18n }}</bit-label>
|
||||
<input type="email" bitInput formControlName="billingEmail" />
|
||||
<bit-hint *ngIf="enableConsolidatedBilling$ | async">{{
|
||||
"providerBillingEmailHint" | i18n
|
||||
}}</bit-hint>
|
||||
<bit-hint>{{ "providerBillingEmailHint" | i18n }}</bit-hint>
|
||||
</bit-form-field>
|
||||
</div>
|
||||
</div>
|
||||
<app-manage-tax-information *ngIf="enableConsolidatedBilling$ | async" />
|
||||
<app-manage-tax-information />
|
||||
<button bitButton bitFormButton buttonType="primary" type="submit">
|
||||
{{ "submit" | i18n }}
|
||||
</button>
|
||||
|
@ -1,14 +1,13 @@
|
||||
import { Component, OnDestroy, OnInit, ViewChild } from "@angular/core";
|
||||
import { FormBuilder, Validators } from "@angular/forms";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { firstValueFrom, Subject, switchMap } from "rxjs";
|
||||
import { Subject, switchMap } from "rxjs";
|
||||
import { first, takeUntil } from "rxjs/operators";
|
||||
|
||||
import { ManageTaxInformationComponent } from "@bitwarden/angular/billing/components";
|
||||
import { ProviderApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider/provider-api.service.abstraction";
|
||||
import { ProviderSetupRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-setup.request";
|
||||
import { ExpandedTaxInfoUpdateRequest } from "@bitwarden/common/billing/models/request/expanded-tax-info-update.request";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
|
||||
@ -34,10 +33,6 @@ export class SetupComponent implements OnInit, OnDestroy {
|
||||
billingEmail: ["", [Validators.required, Validators.email]],
|
||||
});
|
||||
|
||||
protected enableConsolidatedBilling$ = this.configService.getFeatureFlag$(
|
||||
FeatureFlag.EnableConsolidatedBilling,
|
||||
);
|
||||
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
constructor(
|
||||
@ -112,13 +107,9 @@ export class SetupComponent implements OnInit, OnDestroy {
|
||||
|
||||
submit = async () => {
|
||||
try {
|
||||
const consolidatedBillingEnabled = await firstValueFrom(this.enableConsolidatedBilling$);
|
||||
|
||||
this.formGroup.markAllAsTouched();
|
||||
|
||||
const formIsValid = consolidatedBillingEnabled
|
||||
? this.formGroup.valid && this.manageTaxInformationComponent.touch()
|
||||
: this.formGroup.valid;
|
||||
const formIsValid = this.formGroup.valid && this.manageTaxInformationComponent.touch();
|
||||
|
||||
if (!formIsValid) {
|
||||
return;
|
||||
@ -133,19 +124,18 @@ export class SetupComponent implements OnInit, OnDestroy {
|
||||
request.token = this.token;
|
||||
request.key = key;
|
||||
|
||||
if (consolidatedBillingEnabled) {
|
||||
request.taxInfo = new ExpandedTaxInfoUpdateRequest();
|
||||
const taxInformation = this.manageTaxInformationComponent.getTaxInformation();
|
||||
request.taxInfo = new ExpandedTaxInfoUpdateRequest();
|
||||
const taxInformation = this.manageTaxInformationComponent.getTaxInformation();
|
||||
|
||||
request.taxInfo.country = taxInformation.country;
|
||||
request.taxInfo.postalCode = taxInformation.postalCode;
|
||||
if (taxInformation.includeTaxId) {
|
||||
request.taxInfo.taxId = taxInformation.taxId;
|
||||
request.taxInfo.line1 = taxInformation.line1;
|
||||
request.taxInfo.line2 = taxInformation.line2;
|
||||
request.taxInfo.city = taxInformation.city;
|
||||
request.taxInfo.state = taxInformation.state;
|
||||
}
|
||||
request.taxInfo.country = taxInformation.country;
|
||||
request.taxInfo.postalCode = taxInformation.postalCode;
|
||||
|
||||
if (taxInformation.includeTaxId) {
|
||||
request.taxInfo.taxId = taxInformation.taxId;
|
||||
request.taxInfo.line1 = taxInformation.line1;
|
||||
request.taxInfo.line2 = taxInformation.line2;
|
||||
request.taxInfo.city = taxInformation.city;
|
||||
request.taxInfo.state = taxInformation.state;
|
||||
}
|
||||
|
||||
const provider = await this.providerApiService.postProviderSetup(this.providerId, request);
|
||||
|
@ -6,13 +6,11 @@ import { switchMap } from "rxjs/operators";
|
||||
|
||||
import { SearchService } from "@bitwarden/common/abstractions/search.service";
|
||||
import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
|
||||
import { ProviderUserType } from "@bitwarden/common/admin-console/enums";
|
||||
import { ProviderStatusType, ProviderUserType } from "@bitwarden/common/admin-console/enums";
|
||||
import { Provider } from "@bitwarden/common/admin-console/models/domain/provider";
|
||||
import { ProviderOrganizationOrganizationDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-organization.response";
|
||||
import { BillingApiServiceAbstraction as BillingApiService } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction";
|
||||
import { hasConsolidatedBilling } from "@bitwarden/common/billing/abstractions/provider-billing.service.abstraction";
|
||||
import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.response";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
|
||||
import { DialogService, ToastService } from "@bitwarden/components";
|
||||
@ -47,7 +45,6 @@ export class ManageClientsComponent extends BaseClientsComponent {
|
||||
|
||||
constructor(
|
||||
private billingApiService: BillingApiService,
|
||||
private configService: ConfigService,
|
||||
private providerService: ProviderService,
|
||||
private router: Router,
|
||||
activatedRoute: ActivatedRoute,
|
||||
@ -73,9 +70,9 @@ export class ManageClientsComponent extends BaseClientsComponent {
|
||||
switchMap((params) => {
|
||||
this.providerId = params.providerId;
|
||||
return this.providerService.get$(this.providerId).pipe(
|
||||
hasConsolidatedBilling(this.configService),
|
||||
map((hasConsolidatedBilling) => {
|
||||
if (!hasConsolidatedBilling) {
|
||||
map((provider) => provider?.providerStatus === ProviderStatusType.Billable),
|
||||
map((isBillable) => {
|
||||
if (!isBillable) {
|
||||
return from(
|
||||
this.router.navigate(["../clients"], {
|
||||
relativeTo: this.activatedRoute,
|
||||
|
@ -4,24 +4,13 @@ import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
|
||||
import { ProviderStatusType } from "@bitwarden/common/admin-console/enums";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
|
||||
export const hasConsolidatedBilling: CanActivateFn = async (route: ActivatedRouteSnapshot) => {
|
||||
const configService = inject(ConfigService);
|
||||
const providerService = inject(ProviderService);
|
||||
|
||||
const provider = await firstValueFrom(providerService.get$(route.params.providerId));
|
||||
|
||||
const consolidatedBillingEnabled = await configService.getFeatureFlag(
|
||||
FeatureFlag.EnableConsolidatedBilling,
|
||||
);
|
||||
|
||||
if (
|
||||
!consolidatedBillingEnabled ||
|
||||
!provider ||
|
||||
provider.providerStatus !== ProviderStatusType.Billable
|
||||
) {
|
||||
if (!provider || provider.providerStatus !== ProviderStatusType.Billable) {
|
||||
return createUrlTreeFromSnapshot(route, ["/providers", route.params.providerId]);
|
||||
}
|
||||
|
||||
|
@ -713,7 +713,11 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
protected deleteCipher() {
|
||||
const asAdmin = this.organization?.canEditAllCiphers || !this.cipher.collectionIds;
|
||||
// cipher.collectionIds may be null or an empty array. Either is a valid indication that the item is unassigned.
|
||||
const asAdmin =
|
||||
this.organization?.canEditAllCiphers ||
|
||||
!this.cipher.collectionIds ||
|
||||
this.cipher.collectionIds.length === 0;
|
||||
return this.cipher.isDeleted
|
||||
? this.cipherService.deleteWithServer(this.cipher.id, asAdmin)
|
||||
: this.cipherService.softDeleteWithServer(this.cipher.id, asAdmin);
|
||||
|
@ -1,18 +1,19 @@
|
||||
<!-- The calc() reductions are to account for browser/desktop headers -->
|
||||
<main
|
||||
class="tw-flex tw-w-full tw-mx-auto tw-flex-col tw-gap-7 tw-bg-background-alt tw-px-8 tw-pb-4 tw-text-main"
|
||||
class="tw-flex tw-w-full tw-mx-auto tw-flex-col tw-bg-background-alt tw-px-6 tw-pt-6 tw-pb-4 tw-text-main"
|
||||
[ngClass]="{
|
||||
'tw-pt-0': decreaseTopPadding,
|
||||
'tw-pt-8': !decreaseTopPadding,
|
||||
'tw-min-h-screen': clientType === 'web',
|
||||
'tw-min-h-full': clientType === 'browser' || clientType === 'desktop',
|
||||
}"
|
||||
>
|
||||
<a *ngIf="!hideLogo" [routerLink]="['/']" class="tw-w-[128px] [&>*]:tw-align-top">
|
||||
<a
|
||||
*ngIf="!hideLogo"
|
||||
[routerLink]="['/']"
|
||||
class="tw-w-[128px] tw-block tw-mb-12 [&>*]:tw-align-top"
|
||||
>
|
||||
<bit-icon [icon]="logo"></bit-icon>
|
||||
</a>
|
||||
|
||||
<div class="tw-text-center">
|
||||
<div class="tw-text-center tw-mb-6">
|
||||
<div class="tw-mx-auto tw-max-w-28 sm:tw-max-w-32">
|
||||
<bit-icon [icon]="icon"></bit-icon>
|
||||
</div>
|
||||
@ -36,14 +37,14 @@
|
||||
[ngClass]="{ 'tw-max-w-md': maxWidth === 'md', 'tw-max-w-3xl': maxWidth === '3xl' }"
|
||||
>
|
||||
<div
|
||||
class="tw-rounded-2xl tw-mb-9 tw-mx-auto tw-w-full sm:tw-bg-background sm:tw-border sm:tw-border-solid sm:tw-border-secondary-300 sm:tw-p-8"
|
||||
class="tw-rounded-2xl tw-mb-10 tw-mx-auto tw-w-full sm:tw-bg-background sm:tw-border sm:tw-border-solid sm:tw-border-secondary-300 sm:tw-p-8"
|
||||
>
|
||||
<ng-content></ng-content>
|
||||
</div>
|
||||
<ng-content select="[slot=secondary]"></ng-content>
|
||||
</div>
|
||||
|
||||
<footer *ngIf="!hideFooter" class="tw-text-center">
|
||||
<footer *ngIf="!hideFooter" class="tw-text-center tw-mt-6">
|
||||
<div *ngIf="showReadonlyHostname" bitTypography="body2">
|
||||
{{ "accessing" | i18n }} {{ hostname }}
|
||||
</div>
|
||||
|
@ -31,7 +31,7 @@ export class AnonLayoutComponent implements OnInit, OnChanges {
|
||||
@Input() showReadonlyHostname: boolean;
|
||||
@Input() hideLogo: boolean = false;
|
||||
@Input() hideFooter: boolean = false;
|
||||
@Input() decreaseTopPadding: boolean = false;
|
||||
|
||||
/**
|
||||
* Max width of the layout content
|
||||
*
|
||||
|
@ -1,22 +1,102 @@
|
||||
import { svgIcon } from "@bitwarden/components";
|
||||
|
||||
export const UserLockIcon = svgIcon`
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="100" fill="none">
|
||||
<path class="tw-fill-text-headers" fill-rule="evenodd" d="M0 18.207a7.798 7.798 0 0 1 7.798-7.798H89.38a7.798 7.798 0 0 1 7.799 7.798v8.763h-2.4v-8.763a5.399 5.399 0 0 0-5.398-5.399H7.797A5.399 5.399 0 0 0 2.4 18.207v49.19a5.399 5.399 0 0 0 5.4 5.398h9.483v2.4H7.798A7.798 7.798 0 0 1 0 67.396V18.207Zm49.378 54.588h13.498v2.4H49.378v-2.4Z" clip-rule="evenodd"/>
|
||||
<path class="tw-fill-text-headers" fill-rule="evenodd" d="M88.78 58.398c8.946 0 16.197-7.251 16.197-16.196s-7.251-16.197-16.196-16.197-16.197 7.252-16.197 16.197 7.252 16.196 16.197 16.196Zm0 2.4c10.27 0 18.597-8.326 18.597-18.596s-8.326-18.596-18.596-18.596-18.596 8.326-18.596 18.596S78.51 60.798 88.78 60.798Z" clip-rule="evenodd"/>
|
||||
<path class="tw-fill-text-headers" fill-rule="evenodd" d="M61.005 87.192h54.28c1.303 0 1.833-.344 2.01-.53.128-.134.396-.517.226-1.586-2.187-13.74-14.213-24.278-28.752-24.278S62.203 71.337 60.017 85.076c-.09.57.026 1.226.279 1.662.115.2.23.305.316.359.072.046.184.095.393.095Zm0 2.4h54.28c3.346 0 5.104-1.76 4.605-4.893-2.371-14.903-15.402-26.3-31.121-26.3-15.72 0-28.75 11.397-31.122 26.3-.337 2.121.744 4.893 3.358 4.893Z" clip-rule="evenodd"/>
|
||||
<path class="tw-fill-info-600" fill-rule="evenodd" d="M77.983 17.607a1.2 1.2 0 0 1-1.2-1.2v-.6a1.2 1.2 0 1 1 2.4 0v.6a1.2 1.2 0 0 1-1.2 1.2ZM83.382 17.607a1.2 1.2 0 0 1-1.2-1.2v-.498a1.2 1.2 0 1 1 2.4 0v.498a1.2 1.2 0 0 1-1.2 1.2ZM88.78 17.607a1.2 1.2 0 0 1-1.2-1.2v-.498a1.2 1.2 0 1 1 2.4 0v.498a1.2 1.2 0 0 1-1.2 1.2Z" clip-rule="evenodd"/>
|
||||
<path class="tw-fill-text-headers" fill-rule="evenodd" d="M95.083 20.607H0v-1.2h95.083v1.2ZM15.12 54.571a5.999 5.999 0 0 1 6-5.998h23.23a5.999 5.999 0 0 1 6 5.998V57.2h-2.4V54.57a3.6 3.6 0 0 0-3.6-3.599H21.12a3.6 3.6 0 0 0-3.6 3.6v2.627h-2.4V54.57Zm2.4 15.825v2.693a3.6 3.6 0 0 0 3.6 3.599h23.23a3.6 3.6 0 0 0 3.6-3.6v-2.692h2.4v2.693a5.999 5.999 0 0 1-6 5.998H21.12a5.999 5.999 0 0 1-6-5.998v-2.693h2.4Z" clip-rule="evenodd"/>
|
||||
<path class="tw-fill-text-headers" fill-rule="evenodd" d="M32.479 33.255c-5.31 0-9.641 4.332-9.641 9.64v6.822h-2.4v-6.821c0-6.635 5.406-12.04 12.04-12.04 6.633 0 12.041 5.377 12.041 12.04v6.821h-2.4v-6.821c0-5.334-4.33-9.641-9.64-9.641Z" clip-rule="evenodd"/>
|
||||
<path class="tw-fill-info-600" fill-rule="evenodd" d="M10.498 63.797a7.498 7.498 0 0 1 7.498-7.498h30.593a7.498 7.498 0 0 1 0 14.997H17.996a7.498 7.498 0 0 1-7.498-7.499Zm43.79 0a5.699 5.699 0 0 0-5.699-5.699H17.996a5.699 5.699 0 0 0 0 11.398h30.593a5.699 5.699 0 0 0 5.7-5.699Z" clip-rule="evenodd"/>
|
||||
<path class="tw-fill-info-600" fill-rule="evenodd" d="M19.02 60.669a.6.6 0 0 1 .6.6v2.959a.6.6 0 0 1-1.2 0v-2.96a.6.6 0 0 1 .6-.6Z" clip-rule="evenodd"/>
|
||||
<path class="tw-fill-info-600" fill-rule="evenodd" d="M22.443 63.13a.6.6 0 0 1-.388.755l-2.851.914a.6.6 0 1 1-.367-1.142l2.852-.915a.6.6 0 0 1 .754.388Z" clip-rule="evenodd"/>
|
||||
<path class="tw-fill-info-600" fill-rule="evenodd" d="M18.67 63.742a.6.6 0 0 1 .837.135l1.748 2.42a.6.6 0 0 1-.972.703l-1.749-2.42a.6.6 0 0 1 .136-.838Z" clip-rule="evenodd"/>
|
||||
<path class="tw-fill-info-600" fill-rule="evenodd" d="M19.368 63.739a.6.6 0 0 1 .142.837l-1.722 2.42a.6.6 0 0 1-.978-.695l1.722-2.42a.6.6 0 0 1 .836-.142Z" clip-rule="evenodd"/>
|
||||
<path class="tw-fill-info-600" fill-rule="evenodd" d="M15.626 63.129a.6.6 0 0 1 .755-.386l2.825.914a.6.6 0 0 1-.37 1.142l-2.824-.915a.6.6 0 0 1-.386-.755ZM28.651 60.669a.6.6 0 0 1 .6.6v2.959a.6.6 0 1 1-1.2 0v-2.96a.6.6 0 0 1 .6-.6Z" clip-rule="evenodd"/>
|
||||
<path class="tw-fill-info-600" fill-rule="evenodd" d="M32.046 63.129a.6.6 0 0 1-.386.755l-2.824.915a.6.6 0 1 1-.37-1.142l2.825-.914a.6.6 0 0 1 .755.386Z" clip-rule="evenodd"/>
|
||||
<path class="tw-fill-info-600" fill-rule="evenodd" d="M28.3 63.742a.6.6 0 0 1 .837.135l1.749 2.42a.6.6 0 0 1-.973.703l-1.748-2.42a.6.6 0 0 1 .135-.838Z" clip-rule="evenodd"/>
|
||||
<path class="tw-fill-info-600" fill-rule="evenodd" d="M29.002 63.742a.6.6 0 0 1 .136.837L27.387 67a.6.6 0 0 1-.972-.702l1.749-2.421a.6.6 0 0 1 .837-.136Z" clip-rule="evenodd"/>
|
||||
<path class="tw-fill-info-600" fill-rule="evenodd" d="M25.256 63.129a.6.6 0 0 1 .755-.386l2.825.914a.6.6 0 1 1-.37 1.142l-2.824-.915a.6.6 0 0 1-.386-.755ZM34.857 66.649a.6.6 0 0 1 .6-.6h5.649a.6.6 0 0 1 0 1.2h-5.649a.6.6 0 0 1-.6-.6ZM44.487 66.649a.6.6 0 0 1 .6-.6h5.65a.6.6 0 0 1 0 1.2h-5.65a.6.6 0 0 1-.6-.6Z" clip-rule="evenodd"/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 120 100">
|
||||
<path
|
||||
class="tw-fill-art-primary"
|
||||
fill-rule="evenodd"
|
||||
d="M0 18.207a7.798 7.798 0 0 1 7.798-7.798H89.38a7.798 7.798 0 0 1 7.799 7.798v8.763h-2.4v-8.763a5.399 5.399 0 0 0-5.398-5.399H7.797A5.399 5.399 0 0 0 2.4 18.207v49.19a5.399 5.399 0 0 0 5.4 5.398h9.483v2.4H7.798A7.798 7.798 0 0 1 0 67.396V18.207Zm49.378 54.588h13.498v2.4H49.378v-2.4Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
<path
|
||||
class="tw-fill-art-primary"
|
||||
fill-rule="evenodd"
|
||||
d="M88.78 58.398c8.946 0 16.197-7.251 16.197-16.196s-7.251-16.197-16.196-16.197-16.197 7.252-16.197 16.197 7.252 16.196 16.197 16.196Zm0 2.4c10.27 0 18.597-8.326 18.597-18.596s-8.326-18.596-18.596-18.596-18.596 8.326-18.596 18.596S78.51 60.798 88.78 60.798Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
<path
|
||||
class="tw-fill-art-primary"
|
||||
fill-rule="evenodd"
|
||||
d="M61.005 87.192h54.28c1.303 0 1.833-.344 2.01-.53.128-.134.396-.517.226-1.586-2.187-13.74-14.213-24.278-28.752-24.278S62.203 71.337 60.017 85.076c-.09.57.026 1.226.279 1.662.115.2.23.305.316.359.072.046.184.095.393.095Zm0 2.4h54.28c3.346 0 5.104-1.76 4.605-4.893-2.371-14.903-15.402-26.3-31.121-26.3-15.72 0-28.75 11.397-31.122 26.3-.337 2.121.744 4.893 3.358 4.893Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
<path
|
||||
class="tw-fill-art-accent"
|
||||
fill-rule="evenodd"
|
||||
d="M77.983 17.607a1.2 1.2 0 0 1-1.2-1.2v-.6a1.2 1.2 0 1 1 2.4 0v.6a1.2 1.2 0 0 1-1.2 1.2Zm5.399 0a1.2 1.2 0 0 1-1.2-1.2v-.498a1.2 1.2 0 1 1 2.4 0v.498a1.2 1.2 0 0 1-1.2 1.2Zm5.398 0a1.2 1.2 0 0 1-1.2-1.2v-.498a1.2 1.2 0 1 1 2.4 0v.498a1.2 1.2 0 0 1-1.2 1.2Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
<path
|
||||
class="tw-fill-art-primary"
|
||||
fill-rule="evenodd"
|
||||
d="M95.083 20.607H0v-1.2h95.083v1.2ZM15.12 54.571a5.999 5.999 0 0 1 6-5.998h23.23a5.999 5.999 0 0 1 6 5.998V57.2h-2.4v-2.63a3.6 3.6 0 0 0-3.6-3.599H21.12a3.6 3.6 0 0 0-3.6 3.6v2.627h-2.4V54.57Zm2.4 15.825v2.693a3.6 3.6 0 0 0 3.6 3.599h23.23a3.6 3.6 0 0 0 3.6-3.6v-2.692h2.4v2.693a5.999 5.999 0 0 1-6 5.998H21.12a5.999 5.999 0 0 1-6-5.998v-2.693h2.4Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
<path
|
||||
class="tw-fill-art-primary"
|
||||
fill-rule="evenodd"
|
||||
d="M32.479 33.255c-5.31 0-9.641 4.332-9.641 9.64v6.822h-2.4v-6.821c0-6.635 5.406-12.04 12.04-12.04 6.633 0 12.041 5.377 12.041 12.04v6.821h-2.4v-6.821c0-5.334-4.33-9.641-9.64-9.641Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
<path
|
||||
class="tw-fill-art-accent"
|
||||
fill-rule="evenodd"
|
||||
d="M10.498 63.797a7.498 7.498 0 0 1 7.498-7.498h30.593a7.498 7.498 0 0 1 0 14.997H17.996a7.498 7.498 0 0 1-7.498-7.499Zm43.79 0a5.699 5.699 0 0 0-5.699-5.699H17.996a5.699 5.699 0 0 0 0 11.398h30.593a5.699 5.699 0 0 0 5.7-5.699Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
<path
|
||||
class="tw-fill-art-accent"
|
||||
fill-rule="evenodd"
|
||||
d="M19.02 60.669a.6.6 0 0 1 .6.6v2.959a.6.6 0 0 1-1.2 0v-2.96a.6.6 0 0 1 .6-.6Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
<path
|
||||
class="tw-fill-art-accent"
|
||||
fill-rule="evenodd"
|
||||
d="M22.443 63.13a.6.6 0 0 1-.388.755l-2.851.914a.6.6 0 1 1-.367-1.142l2.852-.915a.6.6 0 0 1 .754.388Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
<path
|
||||
class="tw-fill-art-accent"
|
||||
fill-rule="evenodd"
|
||||
d="M18.67 63.742a.6.6 0 0 1 .837.135l1.748 2.42a.6.6 0 0 1-.972.703l-1.749-2.42a.6.6 0 0 1 .136-.838Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
<path
|
||||
class="tw-fill-art-accent"
|
||||
fill-rule="evenodd"
|
||||
d="M19.368 63.739a.6.6 0 0 1 .142.837l-1.722 2.42a.6.6 0 0 1-.978-.695l1.722-2.42a.6.6 0 0 1 .836-.142Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
<path
|
||||
class="tw-fill-art-accent"
|
||||
fill-rule="evenodd"
|
||||
d="M15.626 63.129a.6.6 0 0 1 .755-.386l2.825.914a.6.6 0 0 1-.37 1.142l-2.824-.915a.6.6 0 0 1-.386-.755Zm13.025-2.46a.6.6 0 0 1 .6.6v2.959a.6.6 0 1 1-1.2 0v-2.96a.6.6 0 0 1 .6-.6Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
<path
|
||||
class="tw-fill-art-accent"
|
||||
fill-rule="evenodd"
|
||||
d="M32.046 63.129a.6.6 0 0 1-.386.755l-2.824.915a.6.6 0 1 1-.37-1.142l2.825-.914a.6.6 0 0 1 .755.386Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
<path
|
||||
class="tw-fill-art-accent"
|
||||
fill-rule="evenodd"
|
||||
d="M28.3 63.742a.6.6 0 0 1 .837.135l1.749 2.42a.6.6 0 0 1-.973.703l-1.748-2.42a.6.6 0 0 1 .135-.838Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
<path
|
||||
class="tw-fill-art-accent"
|
||||
fill-rule="evenodd"
|
||||
d="M29.002 63.742a.6.6 0 0 1 .136.837L27.387 67a.6.6 0 0 1-.972-.702l1.749-2.421a.6.6 0 0 1 .837-.136Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
<path
|
||||
class="tw-fill-art-accent"
|
||||
fill-rule="evenodd"
|
||||
d="M25.256 63.129a.6.6 0 0 1 .755-.386l2.825.914a.6.6 0 1 1-.37 1.142l-2.824-.915a.6.6 0 0 1-.386-.755Zm9.601 3.52a.6.6 0 0 1 .6-.6h5.649a.6.6 0 0 1 0 1.2h-5.649a.6.6 0 0 1-.6-.6Zm9.63 0a.6.6 0 0 1 .6-.6h5.65a.6.6 0 0 1 0 1.2h-5.65a.6.6 0 0 1-.6-.6Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
`;
|
||||
|
@ -1,4 +1,3 @@
|
||||
export * from "./account/billing-account-profile-state.service";
|
||||
export * from "./billing-api.service.abstraction";
|
||||
export * from "./organization-billing.service";
|
||||
export * from "./provider-billing.service.abstraction";
|
||||
|
@ -1,23 +0,0 @@
|
||||
import { map, Observable, OperatorFunction, switchMap } from "rxjs";
|
||||
|
||||
import { ProviderStatusType } from "@bitwarden/common/admin-console/enums";
|
||||
import { Provider } from "@bitwarden/common/admin-console/models/domain/provider";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
|
||||
type MaybeProvider = Provider | undefined;
|
||||
|
||||
export const hasConsolidatedBilling = (
|
||||
configService: ConfigService,
|
||||
): OperatorFunction<MaybeProvider, boolean> =>
|
||||
switchMap<MaybeProvider, Observable<boolean>>((provider) =>
|
||||
configService
|
||||
.getFeatureFlag$(FeatureFlag.EnableConsolidatedBilling)
|
||||
.pipe(
|
||||
map((consolidatedBillingEnabled) =>
|
||||
provider
|
||||
? provider.providerStatus === ProviderStatusType.Billable && consolidatedBillingEnabled
|
||||
: false,
|
||||
),
|
||||
),
|
||||
);
|
@ -7,7 +7,6 @@ export enum FeatureFlag {
|
||||
BrowserFilelessImport = "browser-fileless-import",
|
||||
ItemShare = "item-share",
|
||||
GeneratorToolsModernization = "generator-tools-modernization",
|
||||
EnableConsolidatedBilling = "enable-consolidated-billing",
|
||||
AC1795_UpdatedSubscriptionStatusSection = "AC-1795_updated-subscription-status-section",
|
||||
ExtensionRefresh = "extension-refresh",
|
||||
PersistPopupView = "persist-popup-view",
|
||||
@ -58,7 +57,6 @@ export const DefaultFeatureFlagValue = {
|
||||
[FeatureFlag.BrowserFilelessImport]: FALSE,
|
||||
[FeatureFlag.ItemShare]: FALSE,
|
||||
[FeatureFlag.GeneratorToolsModernization]: FALSE,
|
||||
[FeatureFlag.EnableConsolidatedBilling]: FALSE,
|
||||
[FeatureFlag.AC1795_UpdatedSubscriptionStatusSection]: FALSE,
|
||||
[FeatureFlag.ExtensionRefresh]: FALSE,
|
||||
[FeatureFlag.PersistPopupView]: FALSE,
|
||||
|
@ -2,6 +2,7 @@ import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
|
||||
import { DashlaneCsvImporter } from "../src/importers";
|
||||
|
||||
import { credentialsData_otpUrl } from "./test-data/dashlane-csv/credentials-otpurl.csv";
|
||||
import { credentialsData } from "./test-data/dashlane-csv/credentials.csv";
|
||||
import { identityData } from "./test-data/dashlane-csv/id.csv";
|
||||
import { multiplePersonalInfoData } from "./test-data/dashlane-csv/multiple-personal-info.csv";
|
||||
@ -30,6 +31,14 @@ describe("Dashlane CSV Importer", () => {
|
||||
expect(cipher.notes).toEqual("some note for example.com");
|
||||
});
|
||||
|
||||
it("should parse login with totp when given otpUrl instead of otpSecret", async () => {
|
||||
const result = await importer.parse(credentialsData_otpUrl);
|
||||
expect(result != null).toBe(true);
|
||||
|
||||
const cipher = result.ciphers.shift();
|
||||
expect(cipher.login.totp).toEqual("anotherTOTPSeed");
|
||||
});
|
||||
|
||||
it("should parse an item and create a folder", async () => {
|
||||
const result = await importer.parse(credentialsData);
|
||||
|
||||
|
@ -0,0 +1,2 @@
|
||||
export const credentialsData_otpUrl = `username,username2,username3,title,password,note,url,category,otpUrl
|
||||
jdoe,,,example.com,somePassword,some note for example.com,https://www.example.com,Entertainment,anotherTOTPSeed`;
|
@ -119,7 +119,7 @@ export class DashlaneCsvImporter extends BaseImporter implements Importer {
|
||||
cipher.notes = row.note;
|
||||
cipher.login.username = row.username;
|
||||
cipher.login.password = row.password;
|
||||
cipher.login.totp = row.otpSecret;
|
||||
cipher.login.totp = Object.keys(row).includes("otpUrl") ? row.otpUrl : row.otpSecret;
|
||||
cipher.login.uris = this.makeUriArray(row.url);
|
||||
|
||||
this.importUnmappedFields(cipher, row, _mappedCredentialsColumns);
|
||||
|
@ -8,7 +8,8 @@ export class CredentialsRecord {
|
||||
note: string;
|
||||
url: string;
|
||||
category: string;
|
||||
otpSecret: string;
|
||||
otpSecret?: string;
|
||||
otpUrl?: string; // Likely introduced by Dashlane as a replacement for `otpSecret`
|
||||
}
|
||||
|
||||
export class PaymentsRecord {
|
||||
|
@ -3,6 +3,7 @@
|
||||
[placeholder]="'search' | i18n"
|
||||
[(ngModel)]="searchText"
|
||||
(ngModelChange)="onSearchTextChanged()"
|
||||
appAutofocus
|
||||
>
|
||||
</bit-search>
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user