1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-11-28 12:45:45 +01:00

[PM-4419] Add lastpass direct importer to browser (#6638)

* Split up import/export into separate modules

* Fix routing and apply PR feedback

* Renamed OrganizationExport exports to OrganizationVaultExport

* Make import dialogs standalone and move them to libs/importer

* Make import.component re-usable

- Move functionality which was previously present on the org-import.component into import.component
- Move import.component into libs/importer
Make import.component standalone
Create import-web.component to represent Web UI
Fix module imports and routing
Remove unused org-import-files

* Enable importing on deskop

Create import-dialog
Create file-menu entry to open import-dialog
Extend messages.json to include all the necessary messages from shared components

* Renamed filenames according to export rename

* Make ImportWebComponent standalone, simplify routing

* Pass organizationId as Input to ImportComponent

* use formLoading and formDisabled outputs

* use formLoading & formDisabled in desktop

* Emit an event when the import succeeds

Remove Angular router from base-component as other clients might not have routing (i.e. desktop)
Move logic that happened on web successful import into the import-web.component

* Enable importing on deskop

Create import-dialog
Create file-menu entry to open import-dialog
Extend messages.json to include all the necessary messages from shared components

* use formLoading & formDisabled in desktop

* Add missing message for importBlockedByPolicy callout

* Remove commented code for submit button

* Implement onSuccessfulImport to close dialog on success

* fix table themes on desktop & browser

* fix fileSelector button styles

* update selectors to use tools prefix; remove unused selectors

* update selectors

* Wall off UI components in libs/importer

Create barrel-file for libs/importer/components
Remove components and dialog exports from libs/importer/index.ts
Extend libs/shared/tsconfig.libs.json to include @bitwarden/importer/ui -> libs/importer/components
Extend apps/web/tsconfig.ts to include @bitwarden/importer/ui
Update all usages

* Rename @bitwarden/importer to @bitwarden/importer/core

Create more barrel files in libs/importer/*
Update imports within libs/importer
Extend tsconfig files
Update imports in web, desktop, browser and cli

* import-lastpass wip

* Lazy-load the ImportWebComponent via both routes

* Fix import path for ImportComponent

* add validation; add shared folders field

* clean up logic

* fill fileContent on account change

* Use SharedModule as import in import-web.component

* show spinner on pending validation; properly debounce; refactor to loadCSVData func

* fix pending submit guard

* hide on web, show on desktop & browser

* reset user agent fieldset styles

* fix validation

* File selector should be displayed as secondary

* update validation

* Fix setUserTypeContext always throwing

* refactor to password dialog approach

* remove control on destroy; dont submit on enter keydown

* helper to serialize vault accounts (#6556)

* helper to serialize vault accounts

* prettier

* add prompts

* Add missing messages for file-password-prompt

* Add missing messages for import-error-dialog

* Add missing message for import-success-dialog

* Create client-info

* Separate submit and handling import, add error-handling

* Move catch and error handling into submit

* Remove AsyncValidator logic from handleImport

* Add support for filtering shared accounts

* add sso flow to lp import (#6574)

* stub out some sso flow

* use computer props

* lastpass callback

* baseOpenIDConnectAuthority

* openIDConnectAuthorityBase

* comments

* camelCase user type context model

* processSigninResponse

* Refactor handleImport

* use large dialogSize

* remove extra setUserTypeContext

* fix passwordGenerationService provider; pass all errors to ValidationErrors

* add await SSO dialog & logic

* Move lastpass related files into separate folder

* Use bitSubmit to override submit preventDefault (#6607)

Co-authored-by: Daniel James Smith <djsmith85@users.noreply.github.com>

* Use large dialogSize

* revert jslib changes

* PM-4398 - Add missing importWarning

* make ui class methods async

* add LastPassDirectImportService

* update error handling

* add OOB methods (manual passcode only)

* fix typo

* respond to SSO callback

* localize error messages

* remove uneeded comment

* update i18n

* add await sso i18n

* add not implemented error to service

* fix getting k2

* fix k1 bugs

* null checks should not be strict

* update awaiting sso dialog

* update approveDuoWebSdk

* add browser lastpass oidc/sso connector

* add getRedirectUrlWithParams

* params

* rename to getOidcRedirectUrlWithParams

* refactor oob login flow

* Add messages needed for Lastpass import flow

Taken from https://github.com/bitwarden/clients/pull/6541/files#diff-47e9af6d0d7d691a507534f7955edaa9fb37be8cf1c1981fd2ba898e99b6130d

* Update apps/browser/src/connectors/sso.ts

Co-authored-by: Cesar Gonzalez <cesar.a.gonzalezcs@gmail.com>

* Update libs/importer/src/components/lastpass/import-lastpass.component.ts

Co-authored-by: Cesar Gonzalez <cesar.a.gonzalezcs@gmail.com>

* fix error

* Removing fieldset due to merge of https://github.com/bitwarden/clients/pull/6626

* Add sso-connector to manifest.v3

* Make linter happy

* Refactoring to push logic into the service vs the component

Move all methods related to MFA-UI into a LastPassDirectImportUIService
Move all logic around the import into a LastPassDirectImportService
The component now only has the necessary flows but no knowledge on how to use the lastpass import lib or the need for a OIDC client

* Remove unneeded passwordGenerationService

* move all import logic to service

* apply code review: remove name attributes; use protected fields; use formGroup.value

* rename submit method and add comment

* update textarea id

* update i18n

* remove rogue todo comment

* Add missing messages forLastpass import

* extract helper asyncValidatorsFinished

* Remove files related to DuoUI we didn't need to differentiate for MFA via Duo

* Add missing import

* use clientType

* triple =

* lastpassAuthResult for web sso connector

* remove browser sso connector

* use web vault for oidc redirect url

* revert formGroup.value access

* process lastpassAuthResult

* simplify message handler logic

* consolidate logic for lastpass auth result

* swap lastpass logic in sso connector

* add email to signInRequest

* add try again error message

* add try again i18n

* consistent clientinfo id (#6654)



---------

Co-authored-by: William Martin <contact@willmartian.com>

* hide on browser

* show LP importer on browser client

* add missing i18n to browser

* add lastpass prefix

* add shared i18n copy to web and browser

* rename deeplink

* use protected field

* rename el ids

* refactor: remove nested conditional

* update form ids in consuming client components

* remove unnecessary return statement

* fix file id

* use ngIf

* use hidden because of getElementById

* Remove OIDC lib logging

* Forward LP sso callback message to LP direct import service

* Add missing collection label

* Add missing `invalidFilePassword` to messages.json

---------

Co-authored-by: Daniel James Smith <djsmith85@users.noreply.github.com>
Co-authored-by: Daniel James Smith <2670567+djsmith85@users.noreply.github.com>
Co-authored-by: William Martin <contact@willmartian.com>
Co-authored-by: Cesar Gonzalez <cesar.a.gonzalezcs@gmail.com>
This commit is contained in:
Kyle Spearrin 2023-10-24 13:37:48 -05:00 committed by GitHub
parent bf1016f1fd
commit afc9128653
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 112 additions and 19 deletions

View File

@ -2501,6 +2501,9 @@
"importEncKeyError": { "importEncKeyError": {
"message": "Error decrypting the exported file. Your encryption key does not match the encryption key used export the data." "message": "Error decrypting the exported file. Your encryption key does not match the encryption key used export the data."
}, },
"invalidFilePassword": {
"message": "Invalid file password, please use the password you entered when you created the export file."
},
"importDestination": { "importDestination": {
"message": "Import destination" "message": "Import destination"
}, },
@ -2611,8 +2614,65 @@
"useBrowserName": { "useBrowserName": {
"message": "Use browser" "message": "Use browser"
}, },
"multifactorAuthenticationCancelled": {
"message": "Multifactor authentication cancelled"
},
"noLastPassDataFound": {
"message": "No LastPass data found"
},
"incorrectUsernameOrPassword": {
"message": "Incorrect username or password"
},
"multifactorAuthenticationFailed": {
"message": "Multifactor authentication failed"
},
"includeSharedFolders": {
"message": "Include shared folders"
},
"lastPassEmail": {
"message": "LastPass Email"
},
"importingYourAccount": {
"message": "Importing your account..."
},
"lastPassMFARequired": {
"message": "LastPass multifactor authentication required"
},
"lastPassMFADesc": {
"message": "Enter your one-time passcode from your authentication app"
},
"lastPassOOBDesc": {
"message": "Approve the login request in your authentication app or enter a one-time passcode."
},
"passcode": {
"message": "Passcode"
},
"lastPassMasterPassword": {
"message": "LastPass master password"
},
"lastPassAuthRequired": {
"message": "LastPass authentication required"
},
"awaitingSSO": {
"message": "Awaiting SSO authentication"
},
"awaitingSSODesc": {
"message": "Please continue to log in using your company credentials."
},
"seeDetailedInstructions": { "seeDetailedInstructions": {
"message": "See detailed instructions on our help site at", "message": "See detailed instructions on our help site at",
"description": "This is followed a by a hyperlink to the help website." "description": "This is followed a by a hyperlink to the help website."
},
"importDirectlyFromLastPass": {
"message": "Import directly from LastPass"
},
"importFromCSV": {
"message": "Import from CSV"
},
"lastPassTryAgainCheckEmail": {
"message": "Try again or look for an email from LastPass to verify it's you."
},
"collection": {
"message": "Collection"
} }
} }

View File

@ -10,6 +10,7 @@ window.addEventListener(
command: event.data.command, command: event.data.command,
code: event.data.code, code: event.data.code,
state: event.data.state, state: event.data.state,
lastpass: event.data.lastpass,
referrer: event.source.location.hostname, referrer: event.source.location.hostname,
}); });
} }

View File

@ -259,15 +259,22 @@ export default class RuntimeBackground {
return; return;
} }
try { if (msg.lastpass) {
BrowserApi.createNewTab( this.messagingService.send("importCallbackLastPass", {
"popup/index.html?uilocation=popout#/sso?code=" + code: msg.code,
encodeURIComponent(msg.code) + state: msg.state,
"&state=" + });
encodeURIComponent(msg.state) } else {
); try {
} catch { BrowserApi.createNewTab(
this.logService.error("Unable to open sso popout tab"); "popup/index.html?uilocation=popout#/sso?code=" +
encodeURIComponent(msg.code) +
"&state=" +
encodeURIComponent(msg.state)
);
} catch {
this.logService.error("Unable to open sso popout tab");
}
} }
break; break;
} }

View File

@ -5,9 +5,12 @@ require("./sso.scss");
document.addEventListener("DOMContentLoaded", () => { document.addEventListener("DOMContentLoaded", () => {
const code = getQsParam("code"); const code = getQsParam("code");
const state = getQsParam("state"); const state = getQsParam("state");
const lastpass = getQsParam("lp");
if (state != null && state.includes(":clientId=browser")) { if (lastpass === "1") {
initiateBrowserSso(code, state); initiateBrowserSso(code, state, true);
} else if (state != null && state.includes(":clientId=browser")) {
initiateBrowserSso(code, state, false);
} else { } else {
window.location.href = window.location.origin + "/#/sso?code=" + code + "&state=" + state; window.location.href = window.location.origin + "/#/sso?code=" + code + "&state=" + state;
// Match any characters between "_returnUri='" and the next "'" // Match any characters between "_returnUri='" and the next "'"
@ -20,8 +23,8 @@ document.addEventListener("DOMContentLoaded", () => {
} }
}); });
function initiateBrowserSso(code: string, state: string) { function initiateBrowserSso(code: string, state: string, lastpass: boolean) {
window.postMessage({ command: "authResult", code: code, state: state }, "*"); window.postMessage({ command: "authResult", code: code, state: state, lastpass: lastpass }, "*");
const handOffMessage = ("; " + document.cookie) const handOffMessage = ("; " + document.cookie)
.split("; ssoHandOffMessage=") .split("; ssoHandOffMessage=")
.pop() .pop()

View File

@ -188,7 +188,8 @@ export class ImportComponent implements OnInit, OnDestroy {
protected get showLastPassToggle(): boolean { protected get showLastPassToggle(): boolean {
return ( return (
this.format === "lastpasscsv" && this.format === "lastpasscsv" &&
this.platformUtilsService.getClientType() === ClientType.Desktop (this.platformUtilsService.getClientType() === ClientType.Desktop ||
this.platformUtilsService.getClientType() === ClientType.Browser)
); );
} }
protected get showLastPassOptions(): boolean { protected get showLastPassOptions(): boolean {

View File

@ -3,9 +3,11 @@ import { OidcClient } from "oidc-client-ts";
import { Subject, firstValueFrom } from "rxjs"; import { Subject, firstValueFrom } from "rxjs";
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
import { ClientType } from "@bitwarden/common/enums";
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { Utils } from "@bitwarden/common/platform/misc/utils"; import { Utils } from "@bitwarden/common/platform/misc/utils";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
@ -32,13 +34,14 @@ export class LastPassDirectImportService {
constructor( constructor(
private tokenService: TokenService, private tokenService: TokenService,
private cryptoFunctionService: CryptoFunctionService, private cryptoFunctionService: CryptoFunctionService,
private environmentService: EnvironmentService,
private appIdService: AppIdService, private appIdService: AppIdService,
private lastPassDirectImportUIService: LastPassDirectImportUIService, private lastPassDirectImportUIService: LastPassDirectImportUIService,
private platformUtilsService: PlatformUtilsService,
private passwordGenerationService: PasswordGenerationServiceAbstraction, private passwordGenerationService: PasswordGenerationServiceAbstraction,
private broadcasterService: BroadcasterService, private broadcasterService: BroadcasterService,
private ngZone: NgZone, private ngZone: NgZone,
private dialogService: DialogService, private dialogService: DialogService
private platformUtilsService: PlatformUtilsService
) { ) {
this.vault = new Vault(this.cryptoFunctionService, this.tokenService); this.vault = new Vault(this.cryptoFunctionService, this.tokenService);
@ -110,8 +113,7 @@ export class LastPassDirectImportService {
this.oidcClient = new OidcClient({ this.oidcClient = new OidcClient({
authority: this.vault.userType.openIDConnectAuthorityBase, authority: this.vault.userType.openIDConnectAuthorityBase,
client_id: this.vault.userType.openIDConnectClientId, client_id: this.vault.userType.openIDConnectClientId,
// TODO: this is different per client redirect_uri: this.getOidcRedirectUrl(),
redirect_uri: "bitwarden://import-callback-lp",
response_type: "code", response_type: "code",
scope: this.vault.userType.oidcScope, scope: this.vault.userType.oidcScope,
response_mode: "query", response_mode: "query",
@ -131,6 +133,25 @@ export class LastPassDirectImportService {
}); });
} }
private getOidcRedirectUrlWithParams(oidcCode: string, oidcState: string) {
const redirectUri = this.oidcClient.settings.redirect_uri;
const params = "code=" + oidcCode + "&state=" + oidcState;
if (redirectUri.indexOf("bitwarden://") === 0) {
return redirectUri + "/?" + params;
}
return redirectUri + "&" + params;
}
private getOidcRedirectUrl() {
const clientType = this.platformUtilsService.getClientType();
if (clientType === ClientType.Desktop) {
return "bitwarden://import-callback-lp";
}
const webUrl = this.environmentService.getWebVaultUrl();
return webUrl + "/sso-connector.html?lp=1";
}
private async handleStandardImport( private async handleStandardImport(
email: string, email: string,
password: string, password: string,
@ -150,7 +171,7 @@ export class LastPassDirectImportService {
includeSharedFolders: boolean includeSharedFolders: boolean
): Promise<string> { ): Promise<string> {
const response = await this.oidcClient.processSigninResponse( const response = await this.oidcClient.processSigninResponse(
this.oidcClient.settings.redirect_uri + "/?code=" + oidcCode + "&state=" + oidcState this.getOidcRedirectUrlWithParams(oidcCode, oidcState)
); );
const userState = response.userState as any; const userState = response.userState as any;