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: {