From 1d0b549123a415831c4ac5367eb8c12c2b45c4b9 Mon Sep 17 00:00:00 2001 From: Matt Smith Date: Tue, 11 Aug 2020 15:25:07 -0500 Subject: [PATCH 1/8] Modifications made to support SSO in Browser --- src/_locales/en/messages.json | 5 +- src/background/main.background.ts | 13 +++- src/background/runtime.background.ts | 78 +++++++++++++++++--- src/content/sso.ts | 15 ++++ src/manifest.json | 12 +++ src/popup/accounts/home.component.html | 3 + src/popup/accounts/home.component.ts | 40 +++++++++- src/popup/services/services.module.ts | 7 +- src/safari/safari/Info.plist | 4 + src/services/browserPlatformUtils.service.ts | 2 +- webpack.config.js | 1 + 11 files changed, 163 insertions(+), 17 deletions(-) create mode 100644 src/content/sso.ts diff --git a/src/_locales/en/messages.json b/src/_locales/en/messages.json index 64a62017bb..ea0387fc6f 100644 --- a/src/_locales/en/messages.json +++ b/src/_locales/en/messages.json @@ -19,6 +19,9 @@ "login": { "message": "Log In" }, + "enterpriseSingleSignOn": { + "message": "Enterprise Single Sign-On" + }, "cancel": { "message": "Cancel" }, @@ -1297,4 +1300,4 @@ "vaultTimeoutLogOutConfirmationTitle": { "message": "Timeout Action Confirmation" } -} +} \ No newline at end of file diff --git a/src/background/main.background.ts b/src/background/main.background.ts index 885b94b8b2..0191cdd3e1 100644 --- a/src/background/main.background.ts +++ b/src/background/main.background.ts @@ -4,6 +4,7 @@ import { ApiService, AppIdService, AuditService, + AuthService, CipherService, CollectionService, ConstantsService, @@ -13,6 +14,7 @@ import { FolderService, PasswordGenerationService, SettingsService, + StateService, SyncService, TokenService, TotpService, @@ -31,6 +33,7 @@ import { ApiService as ApiServiceAbstraction, AppIdService as AppIdServiceAbstraction, AuditService as AuditServiceAbstraction, + AuthService as AuthServiceAbstraction, CipherService as CipherServiceAbstraction, CollectionService as CollectionServiceAbstraction, CryptoService as CryptoServiceAbstraction, @@ -41,6 +44,7 @@ import { PasswordGenerationService as PasswordGenerationServiceAbstraction, PlatformUtilsService as PlatformUtilsServiceAbstraction, SettingsService as SettingsServiceAbstraction, + StateService as StateServiceAbstraction, StorageService as StorageServiceAbstraction, SyncService as SyncServiceAbstraction, TokenService as TokenServiceAbstraction, @@ -101,9 +105,11 @@ export default class MainBackground { autofillService: AutofillServiceAbstraction; containerService: ContainerService; auditService: AuditServiceAbstraction; + authService: AuthServiceAbstraction; exportService: ExportServiceAbstraction; searchService: SearchServiceAbstraction; notificationsService: NotificationsServiceAbstraction; + stateService: StateServiceAbstraction; systemService: SystemServiceAbstraction; eventService: EventServiceAbstraction; policyService: PolicyServiceAbstraction; @@ -147,6 +153,9 @@ export default class MainBackground { this.apiService = new ApiService(this.tokenService, this.platformUtilsService, (expired: boolean) => this.logout(expired)); this.userService = new UserService(this.tokenService, this.storageService); + this.authService = new AuthService(this.cryptoService, this.apiService, this.userService, + this.tokenService, this.appIdService, this.i18nService, this.platformUtilsService, + this.messagingService, this.vaultTimeoutService, null); this.settingsService = new SettingsService(this.userService, this.storageService); this.cipherService = new CipherService(this.cryptoService, this.userService, this.settingsService, this.apiService, this.storageService, this.i18nService, () => this.searchService); @@ -155,6 +164,7 @@ export default class MainBackground { this.collectionService = new CollectionService(this.cryptoService, this.userService, this.storageService, this.i18nService); this.searchService = new SearchService(this.cipherService, this.platformUtilsService); + this.stateService = new StateService(); this.policyService = new PolicyService(this.userService, this.storageService); this.vaultTimeoutService = new VaultTimeoutService(this.cipherService, this.folderService, this.collectionService, this.cryptoService, this.platformUtilsService, this.storageService, @@ -206,7 +216,8 @@ export default class MainBackground { // Background this.runtimeBackground = new RuntimeBackground(this, this.autofillService, this.cipherService, this.platformUtilsService as BrowserPlatformUtilsService, this.storageService, this.i18nService, - this.analytics, this.notificationsService, this.systemService, this.vaultTimeoutService); + this.analytics, this.notificationsService, this.systemService, this.vaultTimeoutService, this.syncService, + this.authService, this.stateService, this.environmentService); this.commandsBackground = new CommandsBackground(this, this.passwordGenerationService, this.platformUtilsService, this.analytics, this.vaultTimeoutService); diff --git a/src/background/runtime.background.ts b/src/background/runtime.background.ts index 7578f109db..3f2a6420d8 100644 --- a/src/background/runtime.background.ts +++ b/src/background/runtime.background.ts @@ -4,14 +4,18 @@ import { CipherView } from 'jslib/models/view/cipherView'; import { LoginUriView } from 'jslib/models/view/loginUriView'; import { LoginView } from 'jslib/models/view/loginView'; -import { ConstantsService } from 'jslib/services/constants.service'; - -import { I18nService } from 'jslib/abstractions/i18n.service'; - -import { Analytics } from 'jslib/misc'; - +import { AuthResult } from 'jslib/models/domain/authResult'; +import { AuthService } from 'jslib/abstractions/auth.service'; +import { AutofillService } from '../services/abstractions/autofill.service'; +import BrowserPlatformUtilsService from '../services/browserPlatformUtils.service'; import { CipherService } from 'jslib/abstractions/cipher.service'; +import { ConstantsService } from 'jslib/services/constants.service'; +import { EnvironmentService } from 'jslib/abstractions/environment.service'; +import { I18nService } from 'jslib/abstractions/i18n.service'; +import { NotificationsService } from 'jslib/abstractions/notifications.service'; +import { StateService } from 'jslib/abstractions/state.service'; import { StorageService } from 'jslib/abstractions/storage.service'; +import { SyncService } from 'jslib/abstractions/sync.service'; import { SystemService } from 'jslib/abstractions/system.service'; import { VaultTimeoutService } from 'jslib/abstractions/vaultTimeout.service'; @@ -19,11 +23,7 @@ import { BrowserApi } from '../browser/browserApi'; import MainBackground from './main.background'; -import { AutofillService } from '../services/abstractions/autofill.service'; -import BrowserPlatformUtilsService from '../services/browserPlatformUtils.service'; - -import { NotificationsService } from 'jslib/abstractions/notifications.service'; - +import { Analytics } from 'jslib/misc'; import { Utils } from 'jslib/misc/utils'; export default class RuntimeBackground { @@ -33,11 +33,19 @@ export default class RuntimeBackground { private isSafari: boolean; private onInstalledReason: string = null; + formPromise: Promise; + onSuccessfulLoginNavigate: () => Promise; + onSuccessfulLoginTwoFactorNavigate: () => Promise; + loggingIn = false; + private redirectUri = 'https://localhost:8080/sso-connector.html'; + constructor(private main: MainBackground, private autofillService: AutofillService, private cipherService: CipherService, private platformUtilsService: BrowserPlatformUtilsService, private storageService: StorageService, private i18nService: I18nService, private analytics: Analytics, private notificationsService: NotificationsService, - private systemService: SystemService, private vaultTimeoutService: VaultTimeoutService) { + private systemService: SystemService, private vaultTimeoutService: VaultTimeoutService, + private syncService: SyncService, private authService: AuthService, private stateService: StateService, + private environmentService: EnvironmentService) { this.isSafari = this.platformUtilsService.isSafari(); this.runtime = this.isSafari ? {} : chrome.runtime; @@ -47,6 +55,52 @@ export default class RuntimeBackground { this.onInstalledReason = details.reason; }); } + + chrome.runtime.onMessage.addListener( + (request: any) => { + + var vaultUrl = environmentService.webVaultUrl; + if(!vaultUrl) { + vaultUrl = 'https://vault.bitwarden.com'; + // vaultUrl = 'https://localhost:8080'; + } + + if(!request.referrer) { + return; + } + + if(!vaultUrl.includes(request.referrer)) { + return; + } + + if (request.type == "AUTH_RESULT") { + try { + this.logIn(request.code, request.codeVerifier); + } + catch { } + } + }); + } + + async logIn(code: string, codeVerifier: string) { + this.loggingIn = true; + try { + this.formPromise = this.authService.logInSso(code, codeVerifier, this.redirectUri); + const response = await this.formPromise; + + if (response) { + this.syncService.fullSync(true); + this.main.openPopup(); + + var sidebarName : string = this.platformUtilsService.sidebarViewName(); + var sidebarWindows = chrome.extension.getViews({ type: sidebarName }); + if(sidebarWindows && sidebarWindows.length > 0) { + sidebarWindows[0].location.reload(); + } + } + } catch(error) { console.log(error); } + + this.loggingIn = false; } async init() { diff --git a/src/content/sso.ts b/src/content/sso.ts new file mode 100644 index 0000000000..d8e97170e8 --- /dev/null +++ b/src/content/sso.ts @@ -0,0 +1,15 @@ +window.addEventListener("message", function(event) { + if (event.source != window) + return; + + console.log(event.source); + + if (event.data.type && (event.data.type == "AUTH_RESULT")) { + chrome.runtime.sendMessage({ + type: event.data.type, + code: event.data.code, + codeVerifier: event.data.codeVerifier, + referrer: event.source.location.hostname + }); + } +}, false) \ No newline at end of file diff --git a/src/manifest.json b/src/manifest.json index 2f25aa0654..e3ccb6417a 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -41,6 +41,18 @@ ], "run_at": "document_start" }, + { + "all_frames": false, + "js": [ + "content/sso.js" + ], + "matches": [ + "http://*/*", + "https://*/*", + "file:///*" + ], + "run_at": "document_start" + }, { "all_frames": true, "css": [ diff --git a/src/popup/accounts/home.component.html b/src/popup/accounts/home.component.html index a4e1b3393b..3cdfa2e99f 100644 --- a/src/popup/accounts/home.component.html +++ b/src/popup/accounts/home.component.html @@ -3,6 +3,9 @@

{{'loginOrCreateNewAccount' | i18n}}

{{'login' | i18n}} + + {{'enterpriseSingleSignOn' | i18n}} + {{'createAccount' | i18n}} diff --git a/src/popup/accounts/home.component.ts b/src/popup/accounts/home.component.ts index 2780e2b5bc..c967fdc6fd 100644 --- a/src/popup/accounts/home.component.ts +++ b/src/popup/accounts/home.component.ts @@ -1,7 +1,45 @@ import { Component } from '@angular/core'; +import { CryptoFunctionService } from 'jslib/abstractions/cryptoFunction.service'; +import { EnvironmentService } from 'jslib/abstractions/environment.service'; +import { PlatformUtilsService } from '../../../jslib/src/abstractions/platformUtils.service'; +import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service'; + +import { Utils } from 'jslib/misc/utils'; + @Component({ selector: 'app-home', templateUrl: 'home.component.html', }) -export class HomeComponent { } +export class HomeComponent { + constructor( + protected platformUtilsService: PlatformUtilsService, private passwordGenerationService : PasswordGenerationService, + private cryptoFunctionService: CryptoFunctionService, private environmentService: EnvironmentService) { } + + async launchSsoBrowser() { + // Generate necessary sso params + const passwordOptions: any = { + type: 'password', + length: 64, + uppercase: true, + lowercase: true, + numbers: true, + special: false, + }; + + const state = await this.passwordGenerationService.generatePassword(passwordOptions); + let ssoCodeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions); + const codeVerifierHash = await this.cryptoFunctionService.hash(ssoCodeVerifier, 'sha256'); + const codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash); + + const webUrl = 'https://localhost:8080'; + const clientId = 'browser'; + const ssoRedirectUri = 'https://localhost:8080/sso-connector.html'; + + // Launch browser + this.platformUtilsService.launchUri(webUrl + '/#/sso?clientId=' + clientId + + '&redirectUri=' + encodeURIComponent(ssoRedirectUri) + + '&state=' + state + '&codeChallenge=' + codeChallenge + + '&codeVerifier=' + ssoCodeVerifier); + } +} diff --git a/src/popup/services/services.module.ts b/src/popup/services/services.module.ts index 9c1c1d9684..45038cd2cb 100644 --- a/src/popup/services/services.module.ts +++ b/src/popup/services/services.module.ts @@ -21,6 +21,7 @@ import { AuthService as AuthServiceAbstraction } from 'jslib/abstractions/auth.s import { CipherService } from 'jslib/abstractions/cipher.service'; import { CollectionService } from 'jslib/abstractions/collection.service'; import { CryptoService } from 'jslib/abstractions/crypto.service'; +import { CryptoFunctionService } from 'jslib/abstractions/cryptoFunction.service' import { EnvironmentService } from 'jslib/abstractions/environment.service'; import { EventService } from 'jslib/abstractions/event.service'; import { ExportService } from 'jslib/abstractions/export.service'; @@ -40,6 +41,7 @@ import { TokenService } from 'jslib/abstractions/token.service'; import { TotpService } from 'jslib/abstractions/totp.service'; import { UserService } from 'jslib/abstractions/user.service'; import { VaultTimeoutService } from 'jslib/abstractions/vaultTimeout.service'; +import { WebCryptoFunctionService } from 'jslib/services/webCryptoFunction.service'; import { AutofillService } from '../../services/abstractions/autofill.service'; import BrowserMessagingService from '../../services/browserMessaging.service'; @@ -67,9 +69,11 @@ export const authService = new AuthService(getBgService('cryptoSe getBgService('apiService')(), getBgService('userService')(), getBgService('tokenService')(), getBgService('appIdService')(), getBgService('i18nService')(), getBgService('platformUtilsService')(), - messagingService, getBgService('vaultTimeoutService')()); + messagingService, getBgService('vaultTimeoutService')(), null); export const searchService = new PopupSearchService(getBgService('searchService')(), getBgService('cipherService')(), getBgService('platformUtilsService')()); +export const cryptoFunctionService: CryptoFunctionService = new WebCryptoFunctionService(window, + getBgService('platformUtilsService')()); export function initFactory(i18nService: I18nService, storageService: StorageService, popupUtilsService: PopupUtilsService): Function { @@ -121,6 +125,7 @@ export function initFactory(i18nService: I18nService, storageService: StorageSer { provide: AuthServiceAbstraction, useValue: authService }, { provide: StateServiceAbstraction, useValue: stateService }, { provide: SearchServiceAbstraction, useValue: searchService }, + { provide: CryptoFunctionService, useValue: cryptoFunctionService }, { provide: AuditService, useFactory: getBgService('auditService'), deps: [] }, { provide: CipherService, useFactory: getBgService('cipherService'), deps: [] }, { provide: FolderService, useFactory: getBgService('folderService'), deps: [] }, diff --git a/src/safari/safari/Info.plist b/src/safari/safari/Info.plist index 58d3382c82..7676d60e40 100644 --- a/src/safari/safari/Info.plist +++ b/src/safari/safari/Info.plist @@ -53,6 +53,10 @@ Script app/content/shortcuts.js + + Script + app/content/sso.js + SFSafariToolbarItem diff --git a/src/services/browserPlatformUtils.service.ts b/src/services/browserPlatformUtils.service.ts index 0d367e3b65..adf54a564a 100644 --- a/src/services/browserPlatformUtils.service.ts +++ b/src/services/browserPlatformUtils.service.ts @@ -295,7 +295,7 @@ export default class BrowserPlatformUtilsService implements PlatformUtilsService return Promise.resolve(false); } - private sidebarViewName(): string { + sidebarViewName(): string { if ((window as any).chrome.sidebarAction && this.isFirefox()) { return 'sidebar'; } else if (this.isOpera() && (typeof opr !== 'undefined') && opr.sidebarAction) { diff --git a/webpack.config.js b/webpack.config.js index a93e8c9ba2..66dd1c423a 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -136,6 +136,7 @@ const config = { 'content/autofiller': './src/content/autofiller.ts', 'content/notificationBar': './src/content/notificationBar.ts', 'content/shortcuts': './src/content/shortcuts.ts', + 'content/sso': './src/content/sso.ts', 'notification/bar': './src/notification/bar.js', }, optimization: { From e10e006b519d8770fcde9262d20f9100fe967e9f Mon Sep 17 00:00:00 2001 From: Matt Smith Date: Tue, 11 Aug 2020 15:29:11 -0500 Subject: [PATCH 2/8] Removed dev logging --- src/background/runtime.background.ts | 6 +++--- src/content/sso.ts | 2 -- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/background/runtime.background.ts b/src/background/runtime.background.ts index 3f2a6420d8..f4ef117090 100644 --- a/src/background/runtime.background.ts +++ b/src/background/runtime.background.ts @@ -75,14 +75,14 @@ export default class RuntimeBackground { if (request.type == "AUTH_RESULT") { try { - this.logIn(request.code, request.codeVerifier); + this.initiateLogIn(request.code, request.codeVerifier); } catch { } } }); } - async logIn(code: string, codeVerifier: string) { + async initiateLogIn(code: string, codeVerifier: string) { this.loggingIn = true; try { this.formPromise = this.authService.logInSso(code, codeVerifier, this.redirectUri); @@ -98,7 +98,7 @@ export default class RuntimeBackground { sidebarWindows[0].location.reload(); } } - } catch(error) { console.log(error); } + } catch(error) { } this.loggingIn = false; } diff --git a/src/content/sso.ts b/src/content/sso.ts index d8e97170e8..7c71aa14ee 100644 --- a/src/content/sso.ts +++ b/src/content/sso.ts @@ -2,8 +2,6 @@ window.addEventListener("message", function(event) { if (event.source != window) return; - console.log(event.source); - if (event.data.type && (event.data.type == "AUTH_RESULT")) { chrome.runtime.sendMessage({ type: event.data.type, From 09b7d7ec16a16ac1490da1d9f8b9478865087d83 Mon Sep 17 00:00:00 2001 From: Matt Smith Date: Wed, 12 Aug 2020 14:17:55 -0500 Subject: [PATCH 3/8] Cleaned up home.component.ts for feedback. --- src/popup/accounts/home.component.ts | 33 ++++++++++++++++++---------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/src/popup/accounts/home.component.ts b/src/popup/accounts/home.component.ts index c967fdc6fd..eec60dedca 100644 --- a/src/popup/accounts/home.component.ts +++ b/src/popup/accounts/home.component.ts @@ -1,9 +1,11 @@ import { Component } from '@angular/core'; +import { ConstantsService } from 'jslib/services/constants.service' import { CryptoFunctionService } from 'jslib/abstractions/cryptoFunction.service'; import { EnvironmentService } from 'jslib/abstractions/environment.service'; -import { PlatformUtilsService } from '../../../jslib/src/abstractions/platformUtils.service'; +import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service'; import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service'; +import { StorageService } from 'jslib/abstractions/storage.service'; import { Utils } from 'jslib/misc/utils'; @@ -13,8 +15,11 @@ import { Utils } from 'jslib/misc/utils'; }) export class HomeComponent { constructor( - protected platformUtilsService: PlatformUtilsService, private passwordGenerationService : PasswordGenerationService, - private cryptoFunctionService: CryptoFunctionService, private environmentService: EnvironmentService) { } + protected platformUtilsService: PlatformUtilsService, + private passwordGenerationService : PasswordGenerationService, + private cryptoFunctionService: CryptoFunctionService, + private environmentService: EnvironmentService, + private storageService : StorageService) { } async launchSsoBrowser() { // Generate necessary sso params @@ -28,18 +33,24 @@ export class HomeComponent { }; const state = await this.passwordGenerationService.generatePassword(passwordOptions); - let ssoCodeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions); - const codeVerifierHash = await this.cryptoFunctionService.hash(ssoCodeVerifier, 'sha256'); + let codeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions); + const codeVerifierHash = await this.cryptoFunctionService.hash(codeVerifier, 'sha256'); const codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash); - const webUrl = 'https://localhost:8080'; - const clientId = 'browser'; - const ssoRedirectUri = 'https://localhost:8080/sso-connector.html'; + await this.storageService.save(ConstantsService.ssoCodeVerifierKey, codeVerifier); + await this.storageService.save(ConstantsService.ssoStateKey, state); + await this.storageService.save(ConstantsService.ssoClientId, ConstantsService.webClientId); + + let url = this.environmentService.getWebVaultUrl(); + if (url == null) { + url = 'https://vault.bitwarden.com'; + } + + const ssoRedirectUri = url + '/sso-connector.html'; // Launch browser - this.platformUtilsService.launchUri(webUrl + '/#/sso?clientId=' + clientId + + this.platformUtilsService.launchUri(url + '/#/sso?clientId=' + ConstantsService.webClientId + '&redirectUri=' + encodeURIComponent(ssoRedirectUri) + - '&state=' + state + '&codeChallenge=' + codeChallenge + - '&codeVerifier=' + ssoCodeVerifier); + '&state=' + state + '&codeChallenge=' + codeChallenge); } } From 2d56510f0e4931957032909671311ba6f7de7b8c Mon Sep 17 00:00:00 2001 From: Matt Smith Date: Fri, 14 Aug 2020 12:48:50 -0500 Subject: [PATCH 4/8] Modifications made to support browser sso --- src/background/main.background.ts | 5 +- src/background/runtime.background.ts | 35 ++----------- src/content/sso.ts | 2 +- src/popup/accounts/home.component.ts | 9 ++-- src/popup/accounts/login.component.ts | 13 +++-- src/popup/accounts/sso.component.html | 0 src/popup/accounts/sso.component.ts | 60 +++++++++++++++++++++++ src/popup/app-routing.module.ts | 7 +++ src/popup/app.module.ts | 2 + src/popup/services/popup-utils.service.ts | 8 +++ 10 files changed, 99 insertions(+), 42 deletions(-) create mode 100644 src/popup/accounts/sso.component.html create mode 100644 src/popup/accounts/sso.component.ts diff --git a/src/background/main.background.ts b/src/background/main.background.ts index 0191cdd3e1..bbd455a52c 100644 --- a/src/background/main.background.ts +++ b/src/background/main.background.ts @@ -78,6 +78,7 @@ import BrowserMessagingService from '../services/browserMessaging.service'; import BrowserPlatformUtilsService from '../services/browserPlatformUtils.service'; import BrowserStorageService from '../services/browserStorage.service'; import I18nService from '../services/i18n.service'; +import { PopupUtilsService } from '../popup/services/popup-utils.service'; import { AutofillService as AutofillServiceAbstraction } from '../services/abstractions/autofill.service'; @@ -114,6 +115,7 @@ export default class MainBackground { eventService: EventServiceAbstraction; policyService: PolicyServiceAbstraction; analytics: Analytics; + popupUtilsService: PopupUtilsService; onUpdatedRan: boolean; onReplacedRan: boolean; @@ -200,6 +202,7 @@ export default class MainBackground { this.notificationsService); this.analytics = new Analytics(window, () => BrowserApi.gaFilter(), this.platformUtilsService, this.storageService, this.appIdService); + this.popupUtilsService = new PopupUtilsService(this.platformUtilsService); this.systemService = new SystemService(this.storageService, this.vaultTimeoutService, this.messagingService, this.platformUtilsService, () => { const forceWindowReload = this.platformUtilsService.isSafari() || @@ -217,7 +220,7 @@ export default class MainBackground { this.runtimeBackground = new RuntimeBackground(this, this.autofillService, this.cipherService, this.platformUtilsService as BrowserPlatformUtilsService, this.storageService, this.i18nService, this.analytics, this.notificationsService, this.systemService, this.vaultTimeoutService, this.syncService, - this.authService, this.stateService, this.environmentService); + this.authService, this.stateService, this.environmentService, this.popupUtilsService); this.commandsBackground = new CommandsBackground(this, this.passwordGenerationService, this.platformUtilsService, this.analytics, this.vaultTimeoutService); diff --git a/src/background/runtime.background.ts b/src/background/runtime.background.ts index f4ef117090..ecb0aa3955 100644 --- a/src/background/runtime.background.ts +++ b/src/background/runtime.background.ts @@ -13,6 +13,7 @@ import { ConstantsService } from 'jslib/services/constants.service'; import { EnvironmentService } from 'jslib/abstractions/environment.service'; import { I18nService } from 'jslib/abstractions/i18n.service'; import { NotificationsService } from 'jslib/abstractions/notifications.service'; +import { PopupUtilsService } from '../popup/services/popup-utils.service'; import { StateService } from 'jslib/abstractions/state.service'; import { StorageService } from 'jslib/abstractions/storage.service'; import { SyncService } from 'jslib/abstractions/sync.service'; @@ -33,19 +34,13 @@ export default class RuntimeBackground { private isSafari: boolean; private onInstalledReason: string = null; - formPromise: Promise; - onSuccessfulLoginNavigate: () => Promise; - onSuccessfulLoginTwoFactorNavigate: () => Promise; - loggingIn = false; - private redirectUri = 'https://localhost:8080/sso-connector.html'; - constructor(private main: MainBackground, private autofillService: AutofillService, private cipherService: CipherService, private platformUtilsService: BrowserPlatformUtilsService, private storageService: StorageService, private i18nService: I18nService, private analytics: Analytics, private notificationsService: NotificationsService, private systemService: SystemService, private vaultTimeoutService: VaultTimeoutService, private syncService: SyncService, private authService: AuthService, private stateService: StateService, - private environmentService: EnvironmentService) { + private environmentService: EnvironmentService, private popupUtilsService : PopupUtilsService) { this.isSafari = this.platformUtilsService.isSafari(); this.runtime = this.isSafari ? {} : chrome.runtime; @@ -62,7 +57,6 @@ export default class RuntimeBackground { var vaultUrl = environmentService.webVaultUrl; if(!vaultUrl) { vaultUrl = 'https://vault.bitwarden.com'; - // vaultUrl = 'https://localhost:8080'; } if(!request.referrer) { @@ -75,34 +69,13 @@ export default class RuntimeBackground { if (request.type == "AUTH_RESULT") { try { - this.initiateLogIn(request.code, request.codeVerifier); + popupUtilsService.ProcessSso(request.code, request.state); } - catch { } + catch (error) { } } }); } - async initiateLogIn(code: string, codeVerifier: string) { - this.loggingIn = true; - try { - this.formPromise = this.authService.logInSso(code, codeVerifier, this.redirectUri); - const response = await this.formPromise; - - if (response) { - this.syncService.fullSync(true); - this.main.openPopup(); - - var sidebarName : string = this.platformUtilsService.sidebarViewName(); - var sidebarWindows = chrome.extension.getViews({ type: sidebarName }); - if(sidebarWindows && sidebarWindows.length > 0) { - sidebarWindows[0].location.reload(); - } - } - } catch(error) { } - - this.loggingIn = false; - } - async init() { if (!this.runtime) { return; diff --git a/src/content/sso.ts b/src/content/sso.ts index 7c71aa14ee..70ebd0ec22 100644 --- a/src/content/sso.ts +++ b/src/content/sso.ts @@ -6,7 +6,7 @@ window.addEventListener("message", function(event) { chrome.runtime.sendMessage({ type: event.data.type, code: event.data.code, - codeVerifier: event.data.codeVerifier, + state: event.data.state, referrer: event.source.location.hostname }); } diff --git a/src/popup/accounts/home.component.ts b/src/popup/accounts/home.component.ts index eec60dedca..c1504b313a 100644 --- a/src/popup/accounts/home.component.ts +++ b/src/popup/accounts/home.component.ts @@ -32,25 +32,24 @@ export class HomeComponent { special: false, }; - const state = await this.passwordGenerationService.generatePassword(passwordOptions); + const state = (await this.passwordGenerationService.generatePassword(passwordOptions)) + ':clientId=browser'; let codeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions); const codeVerifierHash = await this.cryptoFunctionService.hash(codeVerifier, 'sha256'); const codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash); await this.storageService.save(ConstantsService.ssoCodeVerifierKey, codeVerifier); await this.storageService.save(ConstantsService.ssoStateKey, state); - await this.storageService.save(ConstantsService.ssoClientId, ConstantsService.webClientId); let url = this.environmentService.getWebVaultUrl(); if (url == null) { url = 'https://vault.bitwarden.com'; } - const ssoRedirectUri = url + '/sso-connector.html'; + const redirectUri = url + '/sso-connector.html'; // Launch browser - this.platformUtilsService.launchUri(url + '/#/sso?clientId=' + ConstantsService.webClientId + - '&redirectUri=' + encodeURIComponent(ssoRedirectUri) + + window.open(url + '/#/sso?clientId=browser' + + '&redirectUri=' + encodeURIComponent(redirectUri) + '&state=' + state + '&codeChallenge=' + codeChallenge); } } diff --git a/src/popup/accounts/login.component.ts b/src/popup/accounts/login.component.ts index e0f6cd93c8..66873e538c 100644 --- a/src/popup/accounts/login.component.ts +++ b/src/popup/accounts/login.component.ts @@ -2,7 +2,10 @@ import { Component } from '@angular/core'; import { Router } from '@angular/router'; import { AuthService } from 'jslib/abstractions/auth.service'; +import { CryptoFunctionService } from 'jslib/abstractions/cryptoFunction.service'; +import { EnvironmentService } from 'jslib/abstractions/environment.service'; import { I18nService } from 'jslib/abstractions/i18n.service'; +import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service'; import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service'; import { StateService } from 'jslib/abstractions/state.service'; import { StorageService } from 'jslib/abstractions/storage.service'; @@ -16,10 +19,12 @@ import { LoginComponent as BaseLoginComponent } from 'jslib/angular/components/l }) export class LoginComponent extends BaseLoginComponent { constructor(authService: AuthService, router: Router, - platformUtilsService: PlatformUtilsService, i18nService: I18nService, - syncService: SyncService, storageService: StorageService, - stateService: StateService) { - super(authService, router, platformUtilsService, i18nService, storageService, stateService); + protected platformUtilsService: PlatformUtilsService, protected i18nService: I18nService, + protected stateService: StateService, protected environmentService: EnvironmentService, + protected passwordGenerationService: PasswordGenerationService, + protected cryptoFunctionService: CryptoFunctionService, + storageService: StorageService, syncService : SyncService) { + super(authService, router, platformUtilsService, i18nService, stateService, environmentService, passwordGenerationService, cryptoFunctionService, storageService); super.onSuccessfulLogin = () => { return syncService.fullSync(true); }; diff --git a/src/popup/accounts/sso.component.html b/src/popup/accounts/sso.component.html new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/popup/accounts/sso.component.ts b/src/popup/accounts/sso.component.ts new file mode 100644 index 0000000000..1a0e4a83c6 --- /dev/null +++ b/src/popup/accounts/sso.component.ts @@ -0,0 +1,60 @@ +import { Component } from '@angular/core'; + +import { + ActivatedRoute, + Router, +} from '@angular/router'; + +import { ApiService } from 'jslib/abstractions/api.service'; +import { AuthService } from 'jslib/abstractions/auth.service'; +import BrowserPlatformUtilsService from '../../services/browserPlatformUtils.service'; +import { CryptoFunctionService } from 'jslib/abstractions/cryptoFunction.service'; +import { ConstantsService } from 'jslib/services/constants.service'; +import { EnvironmentService } from 'jslib/abstractions/environment.service'; +import { I18nService } from 'jslib/abstractions/i18n.service'; +import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service'; +import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service'; +import { StateService } from 'jslib/abstractions/state.service'; +import { StorageService } from 'jslib/abstractions/storage.service'; +import { SyncService } from 'jslib/abstractions/sync.service'; + +import { SsoComponent as BaseSsoComponent } from 'jslib/angular/components/sso.component'; + +@Component({ + selector: 'app-sso', + templateUrl: 'sso.component.html', +}) +export class SsoComponent extends BaseSsoComponent { + constructor(authService: AuthService, router: Router, + i18nService: I18nService, route: ActivatedRoute, + storageService: StorageService, stateService: StateService, + platformUtilsService: PlatformUtilsService, apiService: ApiService, + cryptoFunctionService: CryptoFunctionService, passwordGenerationService: PasswordGenerationService, + syncService: SyncService, private browserPlatformUtilsService: BrowserPlatformUtilsService, + private environmentService: EnvironmentService ) { + super(authService, router, i18nService, route, storageService, stateService, platformUtilsService, + apiService, cryptoFunctionService, passwordGenerationService); + + let url = this.environmentService.getWebVaultUrl(); + if (url == null) { + url = 'https://vault.bitwarden.com'; + } + + this.redirectUri = url + '/sso-connector.html'; + this.clientId = 'browser'; + + super.onSuccessfulLogin = () => { + var sidebarName : string = this.browserPlatformUtilsService.sidebarViewName(); + var sidebarWindows = chrome.extension.getViews({ type: sidebarName }); + if(sidebarWindows && sidebarWindows.length > 0) { + sidebarWindows[0].location.reload(); + } + + return syncService.fullSync(true); + }; + + super.onSuccessfulLoginTwoFactorNavigate = () => { + return router.navigate(['2fa']); + } + } +} diff --git a/src/popup/app-routing.module.ts b/src/popup/app-routing.module.ts index b92d9830e0..8a92587a75 100644 --- a/src/popup/app-routing.module.ts +++ b/src/popup/app-routing.module.ts @@ -18,6 +18,7 @@ import { LoginComponent } from './accounts/login.component'; import { RegisterComponent } from './accounts/register.component'; import { TwoFactorOptionsComponent } from './accounts/two-factor-options.component'; import { TwoFactorComponent } from './accounts/two-factor.component'; +import { SsoComponent } from './accounts/sso.component'; import { PasswordGeneratorHistoryComponent } from './generator/password-generator-history.component'; import { PasswordGeneratorComponent } from './generator/password-generator.component'; import { PrivateModeComponent } from './private-mode.component'; @@ -79,6 +80,12 @@ const routes: Routes = [ canActivate: [LaunchGuardService], data: { state: '2fa-options' }, }, + { + path: 'sso', + component: SsoComponent, + canActivate: [LaunchGuardService], + data: { state: 'sso' }, + }, { path: 'register', component: RegisterComponent, diff --git a/src/popup/app.module.ts b/src/popup/app.module.ts index d080cb563d..0f773ba590 100644 --- a/src/popup/app.module.ts +++ b/src/popup/app.module.ts @@ -23,6 +23,7 @@ import { LoginComponent } from './accounts/login.component'; import { RegisterComponent } from './accounts/register.component'; import { TwoFactorOptionsComponent } from './accounts/two-factor-options.component'; import { TwoFactorComponent } from './accounts/two-factor.component'; +import { SsoComponent } from './accounts/sso.component'; import { AppComponent } from './app.component'; import { PasswordGeneratorHistoryComponent } from './generator/password-generator-history.component'; import { PasswordGeneratorComponent } from './generator/password-generator.component'; @@ -206,6 +207,7 @@ registerLocaleData(localeZhTw, 'zh-TW'); TrueFalseValueDirective, TwoFactorOptionsComponent, TwoFactorComponent, + SsoComponent, ViewComponent, ], entryComponents: [], diff --git a/src/popup/services/popup-utils.service.ts b/src/popup/services/popup-utils.service.ts index 2faca3dea7..c7d7f01d45 100644 --- a/src/popup/services/popup-utils.service.ts +++ b/src/popup/services/popup-utils.service.ts @@ -78,4 +78,12 @@ export class PopupUtilsService { // Safari can't open popup in full page tab :( } } + + ProcessSso(code: string, state: string) + { + // Redirect to SSO token validation. + chrome.tabs.create({ + url: 'popup/index.html?uilocation=popout#/sso?code=' + code + '&state=' + state + }); + } } From 3b560fca2211a86c3150ca728619b5cb95e20485 Mon Sep 17 00:00:00 2001 From: Matt Smith Date: Fri, 14 Aug 2020 15:20:16 -0500 Subject: [PATCH 5/8] Implemented feedback --- src/background/runtime.background.ts | 46 +++++++++++------------ src/content/sso.ts | 4 +- src/popup/accounts/sso.component.ts | 14 +------ src/popup/services/popup-utils.service.ts | 8 ---- 4 files changed, 24 insertions(+), 48 deletions(-) diff --git a/src/background/runtime.background.ts b/src/background/runtime.background.ts index ecb0aa3955..3eb8ed350a 100644 --- a/src/background/runtime.background.ts +++ b/src/background/runtime.background.ts @@ -4,7 +4,6 @@ import { CipherView } from 'jslib/models/view/cipherView'; import { LoginUriView } from 'jslib/models/view/loginUriView'; import { LoginView } from 'jslib/models/view/loginView'; -import { AuthResult } from 'jslib/models/domain/authResult'; import { AuthService } from 'jslib/abstractions/auth.service'; import { AutofillService } from '../services/abstractions/autofill.service'; import BrowserPlatformUtilsService from '../services/browserPlatformUtils.service'; @@ -50,30 +49,6 @@ export default class RuntimeBackground { this.onInstalledReason = details.reason; }); } - - chrome.runtime.onMessage.addListener( - (request: any) => { - - var vaultUrl = environmentService.webVaultUrl; - if(!vaultUrl) { - vaultUrl = 'https://vault.bitwarden.com'; - } - - if(!request.referrer) { - return; - } - - if(!vaultUrl.includes(request.referrer)) { - return; - } - - if (request.type == "AUTH_RESULT") { - try { - popupUtilsService.ProcessSso(request.code, request.state); - } - catch (error) { } - } - }); } async init() { @@ -189,6 +164,27 @@ export default class RuntimeBackground { break; } break; + case 'authResult': + var vaultUrl = this.environmentService.webVaultUrl; + if(!vaultUrl) { + vaultUrl = 'https://vault.bitwarden.com'; + } + + if(!msg.referrer) { + return; + } + + if(!vaultUrl.includes(msg.referrer)) { + return; + } + + try { + chrome.tabs.create({ + url: 'popup/index.html?uilocation=popout#/sso?code=' + msg.code + '&state=' + msg.state + }); + } + catch { } + break; default: break; } diff --git a/src/content/sso.ts b/src/content/sso.ts index 70ebd0ec22..cc936d91ab 100644 --- a/src/content/sso.ts +++ b/src/content/sso.ts @@ -2,9 +2,9 @@ window.addEventListener("message", function(event) { if (event.source != window) return; - if (event.data.type && (event.data.type == "AUTH_RESULT")) { + if (event.data.command && (event.data.command == "authResult")) { chrome.runtime.sendMessage({ - type: event.data.type, + command: event.data.command, code: event.data.code, state: event.data.state, referrer: event.source.location.hostname diff --git a/src/popup/accounts/sso.component.ts b/src/popup/accounts/sso.component.ts index 1a0e4a83c6..8a3116e690 100644 --- a/src/popup/accounts/sso.component.ts +++ b/src/popup/accounts/sso.component.ts @@ -9,7 +9,6 @@ import { ApiService } from 'jslib/abstractions/api.service'; import { AuthService } from 'jslib/abstractions/auth.service'; import BrowserPlatformUtilsService from '../../services/browserPlatformUtils.service'; import { CryptoFunctionService } from 'jslib/abstractions/cryptoFunction.service'; -import { ConstantsService } from 'jslib/services/constants.service'; import { EnvironmentService } from 'jslib/abstractions/environment.service'; import { I18nService } from 'jslib/abstractions/i18n.service'; import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service'; @@ -30,8 +29,7 @@ export class SsoComponent extends BaseSsoComponent { storageService: StorageService, stateService: StateService, platformUtilsService: PlatformUtilsService, apiService: ApiService, cryptoFunctionService: CryptoFunctionService, passwordGenerationService: PasswordGenerationService, - syncService: SyncService, private browserPlatformUtilsService: BrowserPlatformUtilsService, - private environmentService: EnvironmentService ) { + syncService: SyncService, private environmentService: EnvironmentService ) { super(authService, router, i18nService, route, storageService, stateService, platformUtilsService, apiService, cryptoFunctionService, passwordGenerationService); @@ -44,17 +42,7 @@ export class SsoComponent extends BaseSsoComponent { this.clientId = 'browser'; super.onSuccessfulLogin = () => { - var sidebarName : string = this.browserPlatformUtilsService.sidebarViewName(); - var sidebarWindows = chrome.extension.getViews({ type: sidebarName }); - if(sidebarWindows && sidebarWindows.length > 0) { - sidebarWindows[0].location.reload(); - } - return syncService.fullSync(true); }; - - super.onSuccessfulLoginTwoFactorNavigate = () => { - return router.navigate(['2fa']); - } } } diff --git a/src/popup/services/popup-utils.service.ts b/src/popup/services/popup-utils.service.ts index c7d7f01d45..2faca3dea7 100644 --- a/src/popup/services/popup-utils.service.ts +++ b/src/popup/services/popup-utils.service.ts @@ -78,12 +78,4 @@ export class PopupUtilsService { // Safari can't open popup in full page tab :( } } - - ProcessSso(code: string, state: string) - { - // Redirect to SSO token validation. - chrome.tabs.create({ - url: 'popup/index.html?uilocation=popout#/sso?code=' + code + '&state=' + state - }); - } } From fb01054539e5c844b946deb1f09b097bbced0ed1 Mon Sep 17 00:00:00 2001 From: Matt Smith Date: Fri, 14 Aug 2020 15:47:39 -0500 Subject: [PATCH 6/8] Updated jslib version --- jslib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jslib b/jslib index 101c5688c4..ed6978baff 160000 --- a/jslib +++ b/jslib @@ -1 +1 @@ -Subproject commit 101c5688c43c4c29d912e6cf6bd5cd45c6bb6926 +Subproject commit ed6978baff5b129341bd46cc90a6155c1bcc5124 From 1075fedad380bc864aaaf26b31c372cc32ba1ae7 Mon Sep 17 00:00:00 2001 From: Matt Smith Date: Mon, 17 Aug 2020 10:52:52 -0500 Subject: [PATCH 7/8] Unable to handle this way - Firefox Conflict --- src/popup/accounts/home.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/popup/accounts/home.component.ts b/src/popup/accounts/home.component.ts index c1504b313a..49ec71d8a8 100644 --- a/src/popup/accounts/home.component.ts +++ b/src/popup/accounts/home.component.ts @@ -48,7 +48,7 @@ export class HomeComponent { const redirectUri = url + '/sso-connector.html'; // Launch browser - window.open(url + '/#/sso?clientId=browser' + + this.platformUtilsService.launchUri(url + '/#/sso?clientId=browser' + '&redirectUri=' + encodeURIComponent(redirectUri) + '&state=' + state + '&codeChallenge=' + codeChallenge); } From 4ea3acf80bac58f6a682862c6c543fd0949f7825 Mon Sep 17 00:00:00 2001 From: Matt Smith Date: Mon, 17 Aug 2020 14:49:01 -0500 Subject: [PATCH 8/8] Centralized BrowserAPI call to refresh firefox --- src/browser/browserApi.ts | 11 +++++++++++ src/popup/accounts/sso.component.ts | 5 ++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/browser/browserApi.ts b/src/browser/browserApi.ts index a536f7a694..5d8282df12 100644 --- a/src/browser/browserApi.ts +++ b/src/browser/browserApi.ts @@ -213,4 +213,15 @@ export class BrowserApi { return chrome.runtime.reload(); } } + + static reloadOpenWindows() { + if(!BrowserApi.isSafariApi) + { + var sidebarName : string = 'sidebar'; + var sidebarWindows = chrome.extension.getViews({ type: sidebarName }); + if(sidebarWindows && sidebarWindows.length > 0) { + sidebarWindows[0].location.reload(); + } + } + } } diff --git a/src/popup/accounts/sso.component.ts b/src/popup/accounts/sso.component.ts index 8a3116e690..e4f9a07c84 100644 --- a/src/popup/accounts/sso.component.ts +++ b/src/popup/accounts/sso.component.ts @@ -7,7 +7,6 @@ import { import { ApiService } from 'jslib/abstractions/api.service'; import { AuthService } from 'jslib/abstractions/auth.service'; -import BrowserPlatformUtilsService from '../../services/browserPlatformUtils.service'; import { CryptoFunctionService } from 'jslib/abstractions/cryptoFunction.service'; import { EnvironmentService } from 'jslib/abstractions/environment.service'; import { I18nService } from 'jslib/abstractions/i18n.service'; @@ -18,6 +17,7 @@ import { StorageService } from 'jslib/abstractions/storage.service'; import { SyncService } from 'jslib/abstractions/sync.service'; import { SsoComponent as BaseSsoComponent } from 'jslib/angular/components/sso.component'; +import { BrowserApi } from '../../browser/browserApi'; @Component({ selector: 'app-sso', @@ -42,6 +42,9 @@ export class SsoComponent extends BaseSsoComponent { this.clientId = 'browser'; super.onSuccessfulLogin = () => { + BrowserApi.reloadOpenWindows(); + const thisWindow = window.open('', '_self'); + thisWindow.close(); return syncService.fullSync(true); }; }