1
0
mirror of https://github.com/bitwarden/directory-connector synced 2025-12-05 23:53:21 +00:00

[chore] Update jslib & state services to match (#212)

* [chore] Update jslib & state services to match

* [bug] Save userId when migrating state

This is used to check for authentication, so if not present on boot of the app authenticated users will still have to log in again

* [bug] Save added accounts with userId

Currently we are passing in an account object, resulting in a null key. We should be passing in a userId

* [bug] Ensure configs and settings are not cleared on logout

We need to persist directoryConfigruations on logout so that logging out and back in doesn't require folks to need to reconfig their settings

* Remove unneeded LoginSyncService

* Run prettier

* [style] Remove commented lines

Co-authored-by: Thomas Rittson <trittson@bitwarden.com>
This commit is contained in:
Addison Beck
2022-01-20 16:31:46 -05:00
committed by GitHub
parent 9a78956b23
commit 9e3d1caee4
8 changed files with 155 additions and 108 deletions

2
jslib

Submodule jslib updated: b0dcc2353f...cf1e483c7f

View File

@@ -43,6 +43,10 @@ import { AuthService } from "../../services/auth.service";
import { StateService } from "../../services/state.service"; import { StateService } from "../../services/state.service";
import { StateMigrationService } from "../../services/stateMigration.service"; import { StateMigrationService } from "../../services/stateMigration.service";
import { Account } from "../../models/account";
import { AccountFactory } from "jslib-common/models/domain/account";
function refreshTokenCallback(injector: Injector) { function refreshTokenCallback(injector: Injector) {
return () => { return () => {
const stateService = injector.get(StateServiceAbstraction); const stateService = injector.get(StateServiceAbstraction);
@@ -197,7 +201,20 @@ export function initFactory(
}, },
{ {
provide: StateServiceAbstraction, provide: StateServiceAbstraction,
useClass: StateService, useFactory: (
storageService: StorageServiceAbstraction,
secureStorageService: StorageServiceAbstraction,
logService: LogServiceAbstraction,
stateMigrationService: StateMigrationServiceAbstraction
) =>
new StateService(
storageService,
secureStorageService,
logService,
stateMigrationService,
true, // TODO: It seems like we aren't applying this from settings anywhere. Is toggling secure storage working?
new AccountFactory(Account)
),
deps: [ deps: [
StorageServiceAbstraction, StorageServiceAbstraction,
"SECURE_STORAGE", "SECURE_STORAGE",

View File

@@ -34,13 +34,15 @@ import { ProviderService } from "jslib-common/services/provider.service";
import { SearchService } from "jslib-common/services/search.service"; import { SearchService } from "jslib-common/services/search.service";
import { SendService } from "jslib-common/services/send.service"; import { SendService } from "jslib-common/services/send.service";
import { SettingsService } from "jslib-common/services/settings.service"; import { SettingsService } from "jslib-common/services/settings.service";
import { SyncService as LoginSyncService } from "jslib-common/services/sync.service";
import { TokenService } from "jslib-common/services/token.service"; import { TokenService } from "jslib-common/services/token.service";
import { StorageService as StorageServiceAbstraction } from "jslib-common/abstractions/storage.service"; import { StorageService as StorageServiceAbstraction } from "jslib-common/abstractions/storage.service";
import { Program } from "./program"; import { Program } from "./program";
import { refreshToken } from "./services/api.service";
import { AccountFactory } from "jslib-common/models/domain/account";
import { Account } from "./models/account";
// tslint:disable-next-line // tslint:disable-next-line
const packageJson = require("./package.json"); const packageJson = require("./package.json");
@@ -72,7 +74,6 @@ export class Main {
syncService: SyncService; syncService: SyncService;
passwordGenerationService: PasswordGenerationService; passwordGenerationService: PasswordGenerationService;
policyService: PolicyService; policyService: PolicyService;
loginSyncService: LoginSyncService;
keyConnectorService: KeyConnectorService; keyConnectorService: KeyConnectorService;
program: Program; program: Program;
stateService: StateService; stateService: StateService;
@@ -130,7 +131,8 @@ export class Main {
this.secureStorageService, this.secureStorageService,
this.logService, this.logService,
this.stateMigrationService, this.stateMigrationService,
process.env.BITWARDENCLI_CONNECTOR_PLAINTEXT_SECRETS !== "true" process.env.BITWARDENCLI_CONNECTOR_PLAINTEXT_SECRETS !== "true",
new AccountFactory(Account)
); );
this.cryptoService = new CryptoService( this.cryptoService = new CryptoService(
@@ -249,24 +251,6 @@ export class Main {
this.providerService = new ProviderService(this.stateService); this.providerService = new ProviderService(this.stateService);
this.loginSyncService = new LoginSyncService(
this.apiService,
this.settingsService,
this.folderService,
this.cipherService,
this.cryptoService,
this.collectionService,
this.messagingService,
this.policyService,
this.sendService,
this.logService,
this.keyConnectorService,
this.stateService,
this.organizationService,
this.providerService,
async (expired: boolean) => this.messagingService.send("logout", { expired: expired })
);
this.program = new Program(this); this.program = new Program(this);
} }

View File

@@ -15,6 +15,10 @@ import { WindowMain } from "jslib-electron/window.main";
import { StateService } from "./services/state.service"; import { StateService } from "./services/state.service";
import { AccountFactory } from "jslib-common/models/domain/account";
import { Account } from "./models/account";
export class Main { export class Main {
logService: ElectronLogService; logService: ElectronLogService;
i18nService: I18nService; i18nService: I18nService;
@@ -54,7 +58,14 @@ export class Main {
this.logService = new ElectronLogService(null, app.getPath("userData")); this.logService = new ElectronLogService(null, app.getPath("userData"));
this.i18nService = new I18nService("en", "./locales/"); this.i18nService = new I18nService("en", "./locales/");
this.storageService = new ElectronStorageService(app.getPath("userData")); this.storageService = new ElectronStorageService(app.getPath("userData"));
this.stateService = new StateService(this.storageService, null, this.logService, null); this.stateService = new StateService(
this.storageService,
null,
this.logService,
null,
true,
new AccountFactory(Account)
);
this.windowMain = new WindowMain( this.windowMain = new WindowMain(
this.stateService, this.stateService,

View File

@@ -16,8 +16,8 @@ export class Account extends BaseAccount {
constructor(init: Partial<Account>) { constructor(init: Partial<Account>) {
super(init); super(init);
this.directoryConfigurations = init.directoryConfigurations ?? new DirectoryConfigurations(); this.directoryConfigurations = init?.directoryConfigurations ?? new DirectoryConfigurations();
this.directorySettings = init.directorySettings ?? new DirectorySettings(); this.directorySettings = init?.directorySettings ?? new DirectorySettings();
} }
} }

View File

@@ -106,9 +106,7 @@ export class Program extends BaseProgram {
this.main.stateService, this.main.stateService,
this.main.cryptoService, this.main.cryptoService,
this.main.policyService, this.main.policyService,
"connector", "connector"
this.main.loginSyncService,
this.main.keyConnectorService
); );
if (!Utils.isNullOrWhitespace(clientId)) { if (!Utils.isNullOrWhitespace(clientId)) {

View File

@@ -1,6 +1,6 @@
import { StateService as BaseStateService } from "jslib-common/services/state.service"; import { StateService as BaseStateService } from "jslib-common/services/state.service";
import { State } from "jslib-common/models/domain/state"; import { AccountFactory } from "jslib-common/models/domain/account";
import { StorageOptions } from "jslib-common/models/domain/storageOptions"; import { StorageOptions } from "jslib-common/models/domain/storageOptions";
import { Account } from "src/models/account"; import { Account } from "src/models/account";
@@ -10,13 +10,15 @@ import { IConfiguration } from "src/models/IConfiguration";
import { LdapConfiguration } from "src/models/ldapConfiguration"; import { LdapConfiguration } from "src/models/ldapConfiguration";
import { OktaConfiguration } from "src/models/oktaConfiguration"; import { OktaConfiguration } from "src/models/oktaConfiguration";
import { OneLoginConfiguration } from "src/models/oneLoginConfiguration"; import { OneLoginConfiguration } from "src/models/oneLoginConfiguration";
import { SyncConfiguration } from "src/models/syncConfiguration";
import { LogService } from "jslib-common/abstractions/log.service"; import { LogService } from "jslib-common/abstractions/log.service";
import { StateMigrationService } from "jslib-common/abstractions/stateMigration.service";
import { StorageService } from "jslib-common/abstractions/storage.service"; import { StorageService } from "jslib-common/abstractions/storage.service";
import { StateService as StateServiceAbstraction } from "src/abstractions/state.service"; import { StateService as StateServiceAbstraction } from "src/abstractions/state.service";
import { DirectoryType } from "src/enums/directoryType"; import { DirectoryType } from "src/enums/directoryType";
import { SyncConfiguration } from "src/models/syncConfiguration";
import { StateMigrationService } from "./stateMigration.service";
const SecureStorageKeys = { const SecureStorageKeys = {
ldap: "ldapPassword", ldap: "ldapPassword",
@@ -31,6 +33,12 @@ const SecureStorageKeys = {
lastSyncHash: "lastSyncHash", lastSyncHash: "lastSyncHash",
}; };
const keys = {
tempAccountSettings: "tempAccountSettings",
tempDirectoryConfigs: "tempDirectoryConfigs",
tempDirectorySettings: "tempDirectorySettings",
};
const StoredSecurely = "[STORED SECURELY]"; const StoredSecurely = "[STORED SECURELY]";
export class StateService extends BaseStateService<Account> implements StateServiceAbstraction { export class StateService extends BaseStateService<Account> implements StateServiceAbstraction {
@@ -39,9 +47,10 @@ export class StateService extends BaseStateService<Account> implements StateServ
protected secureStorageService: StorageService, protected secureStorageService: StorageService,
protected logService: LogService, protected logService: LogService,
protected stateMigrationService: StateMigrationService, protected stateMigrationService: StateMigrationService,
private useSecureStorageForSecrets = true private useSecureStorageForSecrets = true,
protected accountFactory: AccountFactory<Account>
) { ) {
super(storageService, secureStorageService, logService, stateMigrationService); super(storageService, secureStorageService, logService, stateMigrationService, accountFactory);
} }
async getDirectory<T extends IConfiguration>(type: DirectoryType): Promise<T> { async getDirectory<T extends IConfiguration>(type: DirectoryType): Promise<T> {
@@ -520,19 +529,33 @@ export class StateService extends BaseStateService<Account> implements StateServ
} }
protected async scaffoldNewAccountDiskStorage(account: Account): Promise<void> { protected async scaffoldNewAccountDiskStorage(account: Account): Promise<void> {
const storedState = const storedAccount = await this.getAccount(
(await this.storageService.get<State<Account>>( this.reconcileOptions(
"state", { userId: account.profile.userId },
await this.defaultOnDiskLocalOptions() await this.defaultOnDiskLocalOptions()
)) ?? new State<Account>(); )
const storedAccount = storedState.accounts[account.profile.userId]; );
if (storedAccount != null) { if (storedAccount != null) {
account.settings = storedAccount.settings; account.settings = storedAccount.settings;
account.directorySettings = storedAccount.directorySettings; account.directorySettings = storedAccount.directorySettings;
account.directoryConfigurations = storedAccount.directoryConfigurations; account.directoryConfigurations = storedAccount.directoryConfigurations;
} else if (await this.hasTemporaryStorage()) {
// If migrating to state V2 with an no actively authed account we store temporary data to be copied on auth - this will only be run once.
account.settings = await this.storageService.get<any>(keys.tempAccountSettings);
account.directorySettings = await this.storageService.get<any>(keys.tempDirectorySettings);
account.directoryConfigurations = await this.storageService.get<any>(
keys.tempDirectoryConfigs
);
await this.storageService.remove(keys.tempAccountSettings);
await this.storageService.remove(keys.tempDirectorySettings);
await this.storageService.remove(keys.tempDirectoryConfigs);
} }
storedState.accounts[account.profile.userId] = account;
await this.saveStateToStorage(storedState, await this.defaultOnDiskLocalOptions()); await this.storageService.save(
account.profile.userId,
account,
await this.defaultOnDiskLocalOptions()
);
} }
protected async pushAccounts(): Promise<void> { protected async pushAccounts(): Promise<void> {
@@ -542,4 +565,21 @@ export class StateService extends BaseStateService<Account> implements StateServ
} }
this.accounts.next(this.state.accounts); this.accounts.next(this.state.accounts);
} }
protected async hasTemporaryStorage(): Promise<boolean> {
return (
(await this.storageService.has(keys.tempAccountSettings)) ||
(await this.storageService.has(keys.tempDirectorySettings)) ||
(await this.storageService.has(keys.tempDirectoryConfigs))
);
}
protected resetAccount(account: Account) {
const persistentAccountInformation = {
settings: account.settings,
directorySettings: account.directorySettings,
directoryConfigurations: account.directoryConfigurations,
};
return Object.assign(this.createAccount(), persistentAccountInformation);
}
} }

View File

@@ -1,6 +1,3 @@
import { HtmlStorageLocation } from "jslib-common/enums/htmlStorageLocation";
import { State } from "jslib-common/models/domain/state";
import { StateMigrationService as BaseStateMigrationService } from "jslib-common/services/stateMigration.service"; import { StateMigrationService as BaseStateMigrationService } from "jslib-common/services/stateMigration.service";
import { StateVersion } from "jslib-common/enums/stateVersion"; import { StateVersion } from "jslib-common/enums/stateVersion";
@@ -30,7 +27,6 @@ const SecureStorageKeys: { [key: string]: any } = {
}; };
const Keys: { [key: string]: any } = { const Keys: { [key: string]: any } = {
state: "state",
entityId: "entityId", entityId: "entityId",
directoryType: "directoryType", directoryType: "directoryType",
organizationId: "organizationId", organizationId: "organizationId",
@@ -39,6 +35,8 @@ const Keys: { [key: string]: any } = {
lastSyncHash: "lastSyncHash", lastSyncHash: "lastSyncHash",
syncingDir: "syncingDir", syncingDir: "syncingDir",
syncConfig: "syncConfig", syncConfig: "syncConfig",
tempDirectoryConfigs: "tempDirectoryConfigs",
tempDirectorySettings: "tempDirectorySettings",
}; };
const ClientKeys: { [key: string]: any } = { const ClientKeys: { [key: string]: any } = {
@@ -50,8 +48,7 @@ const ClientKeys: { [key: string]: any } = {
export class StateMigrationService extends BaseStateMigrationService { export class StateMigrationService extends BaseStateMigrationService {
async migrate(): Promise<void> { async migrate(): Promise<void> {
let currentStateVersion = let currentStateVersion = await this.getCurrentStateVersion();
(await this.storageService.get<State<Account>>("state"))?.globals?.stateVersion ?? StateVersion.One;
while (currentStateVersion < StateVersion.Latest) { while (currentStateVersion < StateVersion.Latest) {
switch (currentStateVersion) { switch (currentStateVersion) {
case StateVersion.One: case StateVersion.One:
@@ -80,73 +77,73 @@ export class StateMigrationService extends BaseStateMigrationService {
} }
protected async migrateStateFrom1To2(useSecureStorageForSecrets: boolean = true): Promise<void> { protected async migrateStateFrom1To2(useSecureStorageForSecrets: boolean = true): Promise<void> {
// Grabbing a couple of key settings before they get cleared by the base migration
const userId = await this.get<string>(Keys.entityId);
const clientId = await this.get<string>(ClientKeys.clientId);
const clientSecret = await this.get<string>(ClientKeys.clientSecret);
await super.migrateStateFrom1To2(); await super.migrateStateFrom1To2();
const state = await this.storageService.get<State<Account>>(Keys.state);
const userId = await this.storageService.get<string>(Keys.entityId);
if (userId != null) { // Setup reusable method for clearing keys since we will want to do that regardless of if there is an active authenticated session
state.accounts[userId] = new Account({ const clearDirectoryConnectorV1Keys = async () => {
directorySettings: { for (const key in Keys) {
directoryType: await this.storageService.get<DirectoryType>(Keys.directoryType), if (key == null) {
organizationId: await this.storageService.get<string>(Keys.organizationId), continue;
lastUserSync: await this.storageService.get<Date>(Keys.lastUserSync), }
lastGroupSync: await this.storageService.get<Date>(Keys.lastGroupSync), for (const directoryType in DirectoryType) {
lastSyncHash: await this.storageService.get<string>(Keys.lastSyncHash), if (directoryType == null) {
syncingDir: await this.storageService.get<boolean>(Keys.syncingDir), continue;
sync: await this.storageService.get<SyncConfiguration>(Keys.syncConfig), }
}, await this.set(SecureStorageKeys.directoryConfigPrefix + directoryType, null);
profile: {
entityId: await this.storageService.get<string>(Keys.entityId),
},
directoryConfigurations: new DirectoryConfigurations(),
clientKeys: {
clientId: await this.storageService.get<string>(ClientKeys.clientId),
clientSecret: await this.storageService.get<string>(ClientKeys.clientSecret),
},
});
}
for (const key in DirectoryType) {
if (await this.storageService.has(SecureStorageKeys.directoryConfigPrefix + key)) {
switch (+key) {
case DirectoryType.Ldap:
state.accounts[userId].directoryConfigurations.ldap =
await this.storageService.get<LdapConfiguration>(
SecureStorageKeys.directoryConfigPrefix + key
);
break;
case DirectoryType.GSuite:
state.accounts[userId].directoryConfigurations.gsuite =
await this.storageService.get<GSuiteConfiguration>(
SecureStorageKeys.directoryConfigPrefix + key
);
break;
case DirectoryType.AzureActiveDirectory:
state.accounts[userId].directoryConfigurations.azure =
await this.storageService.get<AzureConfiguration>(
SecureStorageKeys.directoryConfigPrefix + key
);
break;
case DirectoryType.Okta:
state.accounts[userId].directoryConfigurations.okta =
await this.storageService.get<OktaConfiguration>(
SecureStorageKeys.directoryConfigPrefix + key
);
break;
case DirectoryType.OneLogin:
state.accounts[userId].directoryConfigurations.oneLogin =
await this.storageService.get<OneLoginConfiguration>(
SecureStorageKeys.directoryConfigPrefix + key
);
break;
} }
await this.storageService.remove(SecureStorageKeys.directoryConfigPrefix + key);
} }
};
// Initilize typed objects from key/value pairs in storage to either be saved temporarily until an account is authed or applied to the active account
const getDirectoryConfig = async <T>(type: DirectoryType) =>
await this.get<T>(SecureStorageKeys.directoryConfigPrefix + type);
const directoryConfigs: DirectoryConfigurations = {
ldap: await getDirectoryConfig<LdapConfiguration>(DirectoryType.Ldap),
gsuite: await getDirectoryConfig<GSuiteConfiguration>(DirectoryType.GSuite),
azure: await getDirectoryConfig<AzureConfiguration>(DirectoryType.AzureActiveDirectory),
okta: await getDirectoryConfig<OktaConfiguration>(DirectoryType.Okta),
oneLogin: await getDirectoryConfig<OneLoginConfiguration>(DirectoryType.OneLogin),
};
const directorySettings: DirectorySettings = {
directoryType: await this.get<DirectoryType>(Keys.directoryType),
organizationId: await this.get<string>(Keys.organizationId),
lastUserSync: await this.get<Date>(Keys.lastUserSync),
lastGroupSync: await this.get<Date>(Keys.lastGroupSync),
lastSyncHash: await this.get<string>(Keys.lastSyncHash),
syncingDir: await this.get<boolean>(Keys.syncingDir),
sync: await this.get<SyncConfiguration>(Keys.syncConfig),
};
// (userId == null) = no authed account, stored data temporarily to be applied and cleared on next auth
// (userId != null) = authed account known, applied stored data to it and do not save temp data
if (userId == null) {
await this.set(Keys.tempDirectoryConfigs, directoryConfigs);
await this.set(Keys.tempDirectorySettings, directorySettings);
await clearDirectoryConnectorV1Keys();
return;
} }
state.globals.environmentUrls = await this.storageService.get("environmentUrls"); const account = await this.get<Account>(userId);
account.directoryConfigurations = directoryConfigs;
account.directorySettings = directorySettings;
account.profile = {
userId: userId,
entityId: userId,
apiKeyClientId: clientId,
};
account.clientKeys = {
clientId: clientId,
clientSecret: clientSecret,
};
await this.storageService.save("state", state); await this.set(userId, account);
await clearDirectoryConnectorV1Keys();
if (useSecureStorageForSecrets) { if (useSecureStorageForSecrets) {
for (const key in SecureStorageKeys) { for (const key in SecureStorageKeys) {