diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 49012ac6..a5af7a2d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -98,7 +98,11 @@ jobs: - name: Version Test run: | - sudo apt install libsecret-1-0 + sudo apt install libsecret-1-0 dbus-x11 gnome-keyring + eval $(dbus-launch --sh-syntax) + + eval $(echo -n "" | /usr/bin/gnome-keyring-daemon --login) + eval $(/usr/bin/gnome-keyring-daemon --components=secrets --start) mkdir -p test/linux unzip ./dist-cli/bwdc-linux-$_PACKAGE_VERSION.zip -d ./test/linux diff --git a/jslib b/jslib index 8fc3cf50..9e263365 160000 --- a/jslib +++ b/jslib @@ -1 +1 @@ -Subproject commit 8fc3cf50d2967212ffbbf0d57cac71d0774aa2a8 +Subproject commit 9e263365497d669c9f7c85a324315a0597a1ea60 diff --git a/src/abstractions/state.service.ts b/src/abstractions/state.service.ts new file mode 100644 index 00000000..db2c589e --- /dev/null +++ b/src/abstractions/state.service.ts @@ -0,0 +1,68 @@ +import { StateService as BaseStateServiceAbstraction } from "jslib-common/abstractions/state.service"; + +import { StorageOptions } from "jslib-common/models/domain/storageOptions"; + +import { DirectoryType } from "src/enums/directoryType"; + +import { Account } from "src/models/account"; +import { AzureConfiguration } from "src/models/azureConfiguration"; +import { GSuiteConfiguration } from "src/models/gsuiteConfiguration"; +import { LdapConfiguration } from "src/models/ldapConfiguration"; +import { OktaConfiguration } from "src/models/oktaConfiguration"; +import { OneLoginConfiguration } from "src/models/oneLoginConfiguration"; +import { SyncConfiguration } from "src/models/syncConfiguration"; + +export abstract class StateService extends BaseStateServiceAbstraction { + getDirectory: (type: DirectoryType) => Promise; + setDirectory: ( + type: DirectoryType, + config: + | LdapConfiguration + | GSuiteConfiguration + | AzureConfiguration + | OktaConfiguration + | OneLoginConfiguration + ) => Promise; + getLdapKey: (options?: StorageOptions) => Promise; + setLdapKey: (value: string, options?: StorageOptions) => Promise; + getGsuiteKey: (options?: StorageOptions) => Promise; + setGsuiteKey: (value: string, options?: StorageOptions) => Promise; + getAzureKey: (options?: StorageOptions) => Promise; + setAzureKey: (value: string, options?: StorageOptions) => Promise; + getOktaKey: (options?: StorageOptions) => Promise; + setOktaKey: (value: string, options?: StorageOptions) => Promise; + getOneLoginKey: (options?: StorageOptions) => Promise; + setOneLoginKey: (value: string, options?: StorageOptions) => Promise; + getLdapConfiguration: (options?: StorageOptions) => Promise; + setLdapConfiguration: (value: LdapConfiguration, options?: StorageOptions) => Promise; + getGsuiteConfiguration: (options?: StorageOptions) => Promise; + setGsuiteConfiguration: (value: GSuiteConfiguration, options?: StorageOptions) => Promise; + getAzureConfiguration: (options?: StorageOptions) => Promise; + setAzureConfiguration: (value: AzureConfiguration, options?: StorageOptions) => Promise; + getOktaConfiguration: (options?: StorageOptions) => Promise; + setOktaConfiguration: (value: OktaConfiguration, options?: StorageOptions) => Promise; + getOneLoginConfiguration: (options?: StorageOptions) => Promise; + setOneLoginConfiguration: ( + value: OneLoginConfiguration, + options?: StorageOptions + ) => Promise; + getOrganizationId: (options?: StorageOptions) => Promise; + setOrganizationId: (value: string, options?: StorageOptions) => Promise; + getSync: (options?: StorageOptions) => Promise; + setSync: (value: SyncConfiguration, options?: StorageOptions) => Promise; + getDirectoryType: (options?: StorageOptions) => Promise; + setDirectoryType: (value: DirectoryType, options?: StorageOptions) => Promise; + getUserDelta: (options?: StorageOptions) => Promise; + setUserDelta: (value: string, options?: StorageOptions) => Promise; + getLastUserSync: (options?: StorageOptions) => Promise; + setLastUserSync: (value: Date, options?: StorageOptions) => Promise; + getLastGroupSync: (options?: StorageOptions) => Promise; + setLastGroupSync: (value: Date, options?: StorageOptions) => Promise; + getGroupDelta: (options?: StorageOptions) => Promise; + setGroupDelta: (value: string, options?: StorageOptions) => Promise; + getLastSyncHash: (options?: StorageOptions) => Promise; + setLastSyncHash: (value: string, options?: StorageOptions) => Promise; + getSyncingDir: (options?: StorageOptions) => Promise; + setSyncingDir: (value: boolean, options?: StorageOptions) => Promise; + clearSyncSettings: (syncHashToo: boolean) => Promise; +} diff --git a/src/app/accounts/apiKey.component.ts b/src/app/accounts/apiKey.component.ts index cfc02e92..7532b3e8 100644 --- a/src/app/accounts/apiKey.component.ts +++ b/src/app/accounts/apiKey.component.ts @@ -1,24 +1,19 @@ -import { - Component, - ComponentFactoryResolver, - Input, - ViewChild, - ViewContainerRef, -} from "@angular/core"; +import { Component, Input, ViewChild, ViewContainerRef } from "@angular/core"; import { Router } from "@angular/router"; import { EnvironmentComponent } from "./environment.component"; -import { ApiKeyService } from "jslib-common/abstractions/apiKey.service"; import { AuthService } from "jslib-common/abstractions/auth.service"; import { I18nService } from "jslib-common/abstractions/i18n.service"; import { LogService } from "jslib-common/abstractions/log.service"; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; +import { StateService } from "../../abstractions/state.service"; + import { ModalService } from "jslib-angular/services/modal.service"; +import { HtmlStorageLocation } from "jslib-common/enums/htmlStorageLocation"; import { Utils } from "jslib-common/misc/utils"; -import { ConfigurationService } from "../../services/configuration.service"; @Component({ selector: "app-apiKey", @@ -36,14 +31,12 @@ export class ApiKeyComponent { constructor( private authService: AuthService, - private apiKeyService: ApiKeyService, private router: Router, private i18nService: I18nService, - private componentFactoryResolver: ComponentFactoryResolver, - private configurationService: ConfigurationService, private platformUtilsService: PlatformUtilsService, private modalService: ModalService, - private logService: LogService + private logService: LogService, + private stateService: StateService ) {} async submit() { @@ -85,8 +78,8 @@ export class ApiKeyComponent { try { this.formPromise = this.authService.logInApiKey(this.clientId, this.clientSecret); await this.formPromise; - const organizationId = await this.apiKeyService.getEntityId(); - await this.configurationService.saveOrganizationId(organizationId); + const organizationId = await this.stateService.getEntityId(); + await this.stateService.setOrganizationId(organizationId); this.router.navigate([this.successRoute]); } catch (e) { this.logService.error(e); diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 79136942..e783ab1c 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -16,13 +16,12 @@ import { I18nService } from "jslib-common/abstractions/i18n.service"; import { LogService } from "jslib-common/abstractions/log.service"; import { MessagingService } from "jslib-common/abstractions/messaging.service"; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; -import { StateService } from "jslib-common/abstractions/state.service"; import { TokenService } from "jslib-common/abstractions/token.service"; -import { UserService } from "jslib-common/abstractions/user.service"; -import { ConfigurationService } from "../services/configuration.service"; import { SyncService } from "../services/sync.service"; +import { StateService } from "../abstractions/state.service"; + const BroadcasterSubscriptionId = "AppComponent"; @Component({ @@ -36,7 +35,6 @@ export class AppComponent implements OnInit { constructor( private broadcasterService: BroadcasterService, - private userService: UserService, private tokenService: TokenService, private authService: AuthService, private router: Router, @@ -46,7 +44,6 @@ export class AppComponent implements OnInit { private ngZone: NgZone, private platformUtilsService: PlatformUtilsService, private messagingService: MessagingService, - private configurationService: ConfigurationService, private syncService: SyncService, private stateService: StateService, private logService: LogService @@ -58,21 +55,21 @@ export class AppComponent implements OnInit { switch (message.command) { case "syncScheduleStarted": case "syncScheduleStopped": - this.stateService.save("syncingDir", message.command === "syncScheduleStarted"); + this.stateService.setSyncingDir(message.command === "syncScheduleStarted"); break; case "logout": this.logOut(!!message.expired); break; case "checkDirSync": try { - const syncConfig = await this.configurationService.getSync(); + const syncConfig = await this.stateService.getSync(); if (syncConfig.interval == null || syncConfig.interval < 5) { return; } const syncInterval = syncConfig.interval * 60000; - const lastGroupSync = await this.configurationService.getLastGroupSyncDate(); - const lastUserSync = await this.configurationService.getLastUserSyncDate(); + const lastGroupSync = await this.stateService.getLastGroupSync(); + const lastUserSync = await this.stateService.getLastUserSync(); let lastSync: Date = null; if (lastGroupSync != null && lastUserSync == null) { lastSync = lastGroupSync; @@ -119,10 +116,8 @@ export class AppComponent implements OnInit { } private async logOut(expired: boolean) { - const userId = await this.userService.getUserId(); - await this.tokenService.clearToken(); - await this.userService.clear(); + await this.stateService.clean(); this.authService.logOut(async () => { if (expired) { diff --git a/src/app/services/auth-guard.service.ts b/src/app/services/auth-guard.service.ts index c184a3c8..367cd286 100644 --- a/src/app/services/auth-guard.service.ts +++ b/src/app/services/auth-guard.service.ts @@ -1,19 +1,16 @@ import { Injectable } from "@angular/core"; -import { CanActivate, Router } from "@angular/router"; -import { ApiKeyService } from "jslib-common/abstractions/apiKey.service"; +import { CanActivate } from "@angular/router"; import { MessagingService } from "jslib-common/abstractions/messaging.service"; +import { StateService } from "../../abstractions/state.service"; + @Injectable() export class AuthGuardService implements CanActivate { - constructor( - private apiKeyService: ApiKeyService, - private router: Router, - private messagingService: MessagingService - ) {} + constructor(private stateService: StateService, private messagingService: MessagingService) {} async canActivate() { - const isAuthed = await this.apiKeyService.isAuthenticated(); + const isAuthed = await this.stateService.getIsAuthenticated(); if (!isAuthed) { this.messagingService.send("logout"); return false; diff --git a/src/app/services/launch-guard.service.ts b/src/app/services/launch-guard.service.ts index 1199bff9..14658f8c 100644 --- a/src/app/services/launch-guard.service.ts +++ b/src/app/services/launch-guard.service.ts @@ -1,14 +1,14 @@ import { Injectable } from "@angular/core"; import { CanActivate, Router } from "@angular/router"; -import { ApiKeyService } from "jslib-common/abstractions/apiKey.service"; +import { StateService } from "../../abstractions/state.service"; @Injectable() export class LaunchGuardService implements CanActivate { - constructor(private apiKeyService: ApiKeyService, private router: Router) {} + constructor(private stateService: StateService, private router: Router) {} async canActivate() { - const isAuthed = await this.apiKeyService.isAuthenticated(); + const isAuthed = await this.stateService.getIsAuthenticated(); if (!isAuthed) { return true; } diff --git a/src/app/services/services.module.ts b/src/app/services/services.module.ts index 774ce526..319ab1d7 100644 --- a/src/app/services/services.module.ts +++ b/src/app/services/services.module.ts @@ -9,23 +9,16 @@ import { ElectronRendererStorageService } from "jslib-electron/services/electron import { AuthGuardService } from "./auth-guard.service"; import { LaunchGuardService } from "./launch-guard.service"; -import { ConfigurationService } from "../../services/configuration.service"; import { I18nService } from "../../services/i18n.service"; import { SyncService } from "../../services/sync.service"; -import { BroadcasterService } from "jslib-angular/services/broadcaster.service"; import { JslibServicesModule } from "jslib-angular/services/jslib-services.module"; -import { ModalService } from "jslib-angular/services/modal.service"; -import { ValidationService } from "jslib-angular/services/validation.service"; -import { ApiKeyService } from "jslib-common/services/apiKey.service"; -import { ConstantsService } from "jslib-common/services/constants.service"; import { ContainerService } from "jslib-common/services/container.service"; import { NodeCryptoFunctionService } from "jslib-node/services/nodeCryptoFunction.service"; import { ApiService as ApiServiceAbstraction } from "jslib-common/abstractions/api.service"; -import { ApiKeyService as ApiKeyServiceAbstraction } from "jslib-common/abstractions/apiKey.service"; import { AppIdService as AppIdServiceAbstraction } from "jslib-common/abstractions/appId.service"; import { AuthService as AuthServiceAbstraction } from "jslib-common/abstractions/auth.service"; import { BroadcasterService as BroadcasterServiceAbstraction } from "jslib-common/abstractions/broadcaster.service"; @@ -36,23 +29,24 @@ import { I18nService as I18nServiceAbstraction } from "jslib-common/abstractions import { KeyConnectorService as KeyConnectorServiceAbstraction } from "jslib-common/abstractions/keyConnector.service"; import { LogService as LogServiceAbstraction } from "jslib-common/abstractions/log.service"; import { MessagingService as MessagingServiceAbstraction } from "jslib-common/abstractions/messaging.service"; -import { PasswordGenerationService as PasswordGenerationServiceAbstraction } from "jslib-common/abstractions/passwordGeneration.service"; import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "jslib-common/abstractions/platformUtils.service"; -import { PolicyService as PolicyServiceAbstraction } from "jslib-common/abstractions/policy.service"; -import { StateService as StateServiceAbstraction } from "jslib-common/abstractions/state.service"; +import { StateMigrationService as StateMigrationServiceAbstraction } from "jslib-common/abstractions/stateMigration.service"; import { StorageService as StorageServiceAbstraction } from "jslib-common/abstractions/storage.service"; import { TokenService as TokenServiceAbstraction } from "jslib-common/abstractions/token.service"; -import { UserService as UserServiceAbstraction } from "jslib-common/abstractions/user.service"; import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "jslib-common/abstractions/vaultTimeout.service"; +import { StateService as StateServiceAbstraction } from "../../abstractions/state.service"; + import { ApiService, refreshToken } from "../../services/api.service"; import { AuthService } from "../../services/auth.service"; +import { StateService } from "../../services/state.service"; +import { StateMigrationService } from "../../services/stateMigration.service"; function refreshTokenCallback(injector: Injector) { return () => { - const apiKeyService = injector.get(ApiKeyServiceAbstraction); + const stateService = injector.get(StateServiceAbstraction); const authService = injector.get(AuthServiceAbstraction); - return refreshToken(apiKeyService, authService); + return refreshToken(stateService, authService); }; } @@ -61,13 +55,11 @@ export function initFactory( i18nService: I18nService, authService: AuthService, platformUtilsService: PlatformUtilsServiceAbstraction, - storageService: StorageServiceAbstraction, - userService: UserServiceAbstraction, - apiService: ApiServiceAbstraction, stateService: StateServiceAbstraction, cryptoService: CryptoServiceAbstraction ): Function { return async () => { + await stateService.init(); await environmentService.setUrlsFromStorage(); await i18nService.init(); authService.init(); @@ -77,7 +69,7 @@ export function initFactory( window.document.title = i18nService.t("bitwardenDirectoryConnector"); let installAction = null; - const installedVersion = await storageService.get(ConstantsService.installedVersionKey); + const installedVersion = await stateService.getInstalledVersion(); const currentVersion = await platformUtilsService.getApplicationVersion(); if (installedVersion == null) { installAction = "install"; @@ -86,16 +78,9 @@ export function initFactory( } if (installAction != null) { - await storageService.save(ConstantsService.installedVersionKey, currentVersion); + await stateService.setInstalledVersion(currentVersion); } - window.setTimeout(async () => { - if (await userService.isAuthenticated()) { - const profile = await apiService.getProfile(); - stateService.save("profileOrganizations", profile.organizations); - } - }, 500); - const containerService = new ContainerService(cryptoService); containerService.attachToWindow(window); }; @@ -113,9 +98,6 @@ export function initFactory( I18nServiceAbstraction, AuthServiceAbstraction, PlatformUtilsServiceAbstraction, - StorageServiceAbstraction, - UserServiceAbstraction, - ApiServiceAbstraction, StateServiceAbstraction, CryptoServiceAbstraction, ], @@ -139,15 +121,11 @@ export function initFactory( useFactory: ( i18nService: I18nServiceAbstraction, messagingService: MessagingServiceAbstraction, - storageService: StorageServiceAbstraction - ) => new ElectronPlatformUtilsService(i18nService, messagingService, true, storageService), - deps: [I18nServiceAbstraction, MessagingServiceAbstraction, StorageServiceAbstraction], - }, - { - provide: CryptoFunctionServiceAbstraction, - useClass: NodeCryptoFunctionService, - deps: [], + stateService: StateServiceAbstraction + ) => new ElectronPlatformUtilsService(i18nService, messagingService, true, stateService), + deps: [I18nServiceAbstraction, MessagingServiceAbstraction, StateServiceAbstraction], }, + { provide: CryptoFunctionServiceAbstraction, useClass: NodeCryptoFunctionService, deps: [] }, { provide: ApiServiceAbstraction, useFactory: ( @@ -172,18 +150,12 @@ export function initFactory( Injector, ], }, - { - provide: ApiKeyServiceAbstraction, - useClass: ApiKeyService, - deps: [TokenServiceAbstraction, StorageServiceAbstraction], - }, { provide: AuthServiceAbstraction, useClass: AuthService, deps: [ CryptoServiceAbstraction, ApiServiceAbstraction, - UserServiceAbstraction, TokenServiceAbstraction, AppIdServiceAbstraction, I18nServiceAbstraction, @@ -191,32 +163,42 @@ export function initFactory( MessagingServiceAbstraction, VaultTimeoutServiceAbstraction, LogServiceAbstraction, - ApiKeyServiceAbstraction, CryptoFunctionServiceAbstraction, EnvironmentServiceAbstraction, KeyConnectorServiceAbstraction, + StateServiceAbstraction, ], }, - { - provide: ConfigurationService, - useClass: ConfigurationService, - deps: [StorageServiceAbstraction, "SECURE_STORAGE"], - }, { provide: SyncService, useClass: SyncService, deps: [ - ConfigurationService, LogServiceAbstraction, CryptoFunctionServiceAbstraction, ApiServiceAbstraction, MessagingServiceAbstraction, I18nServiceAbstraction, EnvironmentServiceAbstraction, + StateServiceAbstraction, ], }, AuthGuardService, LaunchGuardService, + { + provide: StateMigrationServiceAbstraction, + useClass: StateMigrationService, + deps: [StorageServiceAbstraction, "SECURE_STORAGE"], + }, + { + provide: StateServiceAbstraction, + useClass: StateService, + deps: [ + StorageServiceAbstraction, + "SECURE_STORAGE", + LogServiceAbstraction, + StateMigrationServiceAbstraction, + ], + }, ], }) export class ServicesModule {} diff --git a/src/app/tabs/dashboard.component.ts b/src/app/tabs/dashboard.component.ts index e65b4b1c..7dcff278 100644 --- a/src/app/tabs/dashboard.component.ts +++ b/src/app/tabs/dashboard.component.ts @@ -4,17 +4,17 @@ import { BroadcasterService } from "jslib-common/abstractions/broadcaster.servic import { I18nService } from "jslib-common/abstractions/i18n.service"; import { MessagingService } from "jslib-common/abstractions/messaging.service"; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; -import { StateService } from "jslib-common/abstractions/state.service"; import { SyncService } from "../../services/sync.service"; import { GroupEntry } from "../../models/groupEntry"; import { SimResult } from "../../models/simResult"; import { UserEntry } from "../../models/userEntry"; -import { ConfigurationService } from "../../services/configuration.service"; import { ConnectorUtils } from "../../utils"; +import { StateService } from "../../abstractions/state.service"; + const BroadcasterSubscriptionId = "DashboardComponent"; @Component({ @@ -38,7 +38,6 @@ export class DashboardComponent implements OnInit, OnDestroy { constructor( private i18nService: I18nService, private syncService: SyncService, - private configurationService: ConfigurationService, private broadcasterService: BroadcasterService, private ngZone: NgZone, private messagingService: MessagingService, @@ -62,7 +61,7 @@ export class DashboardComponent implements OnInit, OnDestroy { }); }); - this.syncRunning = !!(await this.stateService.get("syncingDir")); + this.syncRunning = !!(await this.stateService.getSyncingDir()); this.updateLastSync(); } @@ -122,7 +121,7 @@ export class DashboardComponent implements OnInit, OnDestroy { } private async updateLastSync() { - this.lastGroupSync = await this.configurationService.getLastGroupSyncDate(); - this.lastUserSync = await this.configurationService.getLastUserSyncDate(); + this.lastGroupSync = await this.stateService.getLastGroupSync(); + this.lastUserSync = await this.stateService.getLastUserSync(); } } diff --git a/src/app/tabs/more.component.ts b/src/app/tabs/more.component.ts index d3e4bab8..198638bd 100644 --- a/src/app/tabs/more.component.ts +++ b/src/app/tabs/more.component.ts @@ -1,11 +1,11 @@ -import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit } from "@angular/core"; +import { ChangeDetectorRef, Component, NgZone, OnInit } from "@angular/core"; import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service"; import { I18nService } from "jslib-common/abstractions/i18n.service"; import { MessagingService } from "jslib-common/abstractions/messaging.service"; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; -import { ConfigurationService } from "../../services/configuration.service"; +import { StateService } from "../../abstractions/state.service"; const BroadcasterSubscriptionId = "MoreComponent"; @@ -22,10 +22,10 @@ export class MoreComponent implements OnInit { private platformUtilsService: PlatformUtilsService, private i18nService: I18nService, private messagingService: MessagingService, - private configurationService: ConfigurationService, private broadcasterService: BroadcasterService, private ngZone: NgZone, - private changeDetectorRef: ChangeDetectorRef + private changeDetectorRef: ChangeDetectorRef, + private stateService: StateService ) {} async ngOnInit() { @@ -71,7 +71,7 @@ export class MoreComponent implements OnInit { } async clearCache() { - await this.configurationService.clearStatefulSettings(true); + await this.stateService.clearSyncSettings(true); this.platformUtilsService.showToast("success", null, this.i18nService.t("syncCacheCleared")); } } diff --git a/src/app/tabs/settings.component.ts b/src/app/tabs/settings.component.ts index 2c1be5fe..6210512c 100644 --- a/src/app/tabs/settings.component.ts +++ b/src/app/tabs/settings.component.ts @@ -2,11 +2,6 @@ import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit } from "@angula import { I18nService } from "jslib-common/abstractions/i18n.service"; import { LogService } from "jslib-common/abstractions/log.service"; -import { StateService } from "jslib-common/abstractions/state.service"; - -import { ProfileOrganizationResponse } from "jslib-common/models/response/profileOrganizationResponse"; - -import { ConfigurationService } from "../../services/configuration.service"; import { DirectoryType } from "../../enums/directoryType"; @@ -17,6 +12,7 @@ import { OktaConfiguration } from "../../models/oktaConfiguration"; import { OneLoginConfiguration } from "../../models/oneLoginConfiguration"; import { SyncConfiguration } from "../../models/syncConfiguration"; +import { StateService } from "../../abstractions/state.service"; import { ConnectorUtils } from "../../utils"; @Component({ @@ -40,14 +36,13 @@ export class SettingsComponent implements OnInit, OnDestroy { constructor( private i18nService: I18nService, - private configurationService: ConfigurationService, private changeDetectorRef: ChangeDetectorRef, private ngZone: NgZone, - private stateService: StateService, - private logService: LogService + private logService: LogService, + private stateService: StateService ) { this.directoryOptions = [ - { name: i18nService.t("select"), value: null }, + { name: this.i18nService.t("select"), value: null }, { name: "Active Directory / LDAP", value: DirectoryType.Ldap }, { name: "Azure Active Directory", value: DirectoryType.AzureActiveDirectory }, { name: "G Suite (Google)", value: DirectoryType.GSuite }, @@ -57,25 +52,22 @@ export class SettingsComponent implements OnInit, OnDestroy { } async ngOnInit() { - this.directory = await this.configurationService.getDirectoryType(); + this.directory = await this.stateService.getDirectoryType(); this.ldap = - (await this.configurationService.getDirectory(DirectoryType.Ldap)) || - this.ldap; + (await this.stateService.getDirectory(DirectoryType.Ldap)) || this.ldap; this.gsuite = - (await this.configurationService.getDirectory(DirectoryType.GSuite)) || + (await this.stateService.getDirectory(DirectoryType.GSuite)) || this.gsuite; this.azure = - (await this.configurationService.getDirectory( + (await this.stateService.getDirectory( DirectoryType.AzureActiveDirectory )) || this.azure; this.okta = - (await this.configurationService.getDirectory(DirectoryType.Okta)) || - this.okta; + (await this.stateService.getDirectory(DirectoryType.Okta)) || this.okta; this.oneLogin = - (await this.configurationService.getDirectory( - DirectoryType.OneLogin - )) || this.oneLogin; - this.sync = (await this.configurationService.getSync()) || this.sync; + (await this.stateService.getDirectory(DirectoryType.OneLogin)) || + this.oneLogin; + this.sync = (await this.stateService.getSync()) || this.sync; } async ngOnDestroy() { @@ -87,13 +79,13 @@ export class SettingsComponent implements OnInit, OnDestroy { if (this.ldap != null && this.ldap.ad) { this.ldap.pagedSearch = true; } - await this.configurationService.saveDirectoryType(this.directory); - await this.configurationService.saveDirectory(DirectoryType.Ldap, this.ldap); - await this.configurationService.saveDirectory(DirectoryType.GSuite, this.gsuite); - await this.configurationService.saveDirectory(DirectoryType.AzureActiveDirectory, this.azure); - await this.configurationService.saveDirectory(DirectoryType.Okta, this.okta); - await this.configurationService.saveDirectory(DirectoryType.OneLogin, this.oneLogin); - await this.configurationService.saveSync(this.sync); + await this.stateService.setDirectoryType(this.directory); + await this.stateService.setDirectory(DirectoryType.Ldap, this.ldap); + await this.stateService.setDirectory(DirectoryType.GSuite, this.gsuite); + await this.stateService.setDirectory(DirectoryType.AzureActiveDirectory, this.azure); + await this.stateService.setDirectory(DirectoryType.Okta, this.okta); + await this.stateService.setDirectory(DirectoryType.OneLogin, this.oneLogin); + await this.stateService.setSync(this.sync); } parseKeyFile() { diff --git a/src/bwdc.ts b/src/bwdc.ts index 7f2a8f46..31f88da0 100644 --- a/src/bwdc.ts +++ b/src/bwdc.ts @@ -5,22 +5,21 @@ import { LogLevelType } from "jslib-common/enums/logLevelType"; import { AuthService } from "./services/auth.service"; -import { ConfigurationService } from "./services/configuration.service"; import { I18nService } from "./services/i18n.service"; import { KeytarSecureStorageService } from "./services/keytarSecureStorage.service"; import { LowdbStorageService } from "./services/lowdbStorage.service"; import { NodeApiService } from "./services/nodeApi.service"; +import { StateService } from "./services/state.service"; +import { StateMigrationService } from "./services/stateMigration.service"; import { SyncService } from "./services/sync.service"; import { CliPlatformUtilsService } from "jslib-node/cli/services/cliPlatformUtils.service"; import { ConsoleLogService } from "jslib-node/cli/services/consoleLog.service"; import { NodeCryptoFunctionService } from "jslib-node/services/nodeCryptoFunction.service"; -import { ApiKeyService } from "jslib-common/services/apiKey.service"; import { AppIdService } from "jslib-common/services/appId.service"; import { CipherService } from "jslib-common/services/cipher.service"; import { CollectionService } from "jslib-common/services/collection.service"; -import { ConstantsService } from "jslib-common/services/constants.service"; import { ContainerService } from "jslib-common/services/container.service"; import { CryptoService } from "jslib-common/services/crypto.service"; import { EnvironmentService } from "jslib-common/services/environment.service"; @@ -28,14 +27,15 @@ import { FileUploadService } from "jslib-common/services/fileUpload.service"; import { FolderService } from "jslib-common/services/folder.service"; import { KeyConnectorService } from "jslib-common/services/keyConnector.service"; import { NoopMessagingService } from "jslib-common/services/noopMessaging.service"; +import { OrganizationService } from "jslib-common/services/organization.service"; import { PasswordGenerationService } from "jslib-common/services/passwordGeneration.service"; import { PolicyService } from "jslib-common/services/policy.service"; +import { ProviderService } from "jslib-common/services/provider.service"; import { SearchService } from "jslib-common/services/search.service"; import { SendService } from "jslib-common/services/send.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 { UserService } from "jslib-common/services/user.service"; import { StorageService as StorageServiceAbstraction } from "jslib-common/abstractions/storage.service"; @@ -54,18 +54,14 @@ export class Main { secureStorageService: StorageServiceAbstraction; i18nService: I18nService; platformUtilsService: CliPlatformUtilsService; - constantsService: ConstantsService; cryptoService: CryptoService; tokenService: TokenService; appIdService: AppIdService; apiService: NodeApiService; environmentService: EnvironmentService; - apiKeyService: ApiKeyService; - userService: UserService; containerService: ContainerService; cryptoFunctionService: NodeCryptoFunctionService; authService: AuthService; - configurationService: ConfigurationService; collectionService: CollectionService; cipherService: CipherService; fileUploadService: FileUploadService; @@ -79,6 +75,10 @@ export class Main { loginSyncService: LoginSyncService; keyConnectorService: KeyConnectorService; program: Program; + stateService: StateService; + stateMigrationService: StateMigrationService; + organizationService: OrganizationService; + providerService: ProviderService; constructor() { const applicationName = "Bitwarden Directory Connector"; @@ -119,22 +119,36 @@ export class Main { this.secureStorageService = plaintextSecrets ? this.storageService : new KeytarSecureStorageService(applicationName); - this.cryptoService = new CryptoService( + + this.stateMigrationService = new StateMigrationService( + this.storageService, + this.secureStorageService + ); + + this.stateService = new StateService( this.storageService, this.secureStorageService, + this.logService, + this.stateMigrationService, + process.env.BITWARDENCLI_CONNECTOR_PLAINTEXT_SECRETS !== "true" + ); + + this.cryptoService = new CryptoService( this.cryptoFunctionService, this.platformUtilsService, - this.logService + this.logService, + this.stateService ); + this.appIdService = new AppIdService(this.storageService); - this.tokenService = new TokenService(this.storageService); + this.tokenService = new TokenService(this.stateService); this.messagingService = new NoopMessagingService(); - this.environmentService = new EnvironmentService(this.storageService); + this.environmentService = new EnvironmentService(this.stateService); this.apiService = new NodeApiService( this.tokenService, this.platformUtilsService, this.environmentService, - () => refreshToken(this.apiKeyService, this.authService), + () => refreshToken(this.stateService, this.authService), async (expired: boolean) => await this.logout(), "Bitwarden_DC/" + this.platformUtilsService.getApplicationVersion() + @@ -143,21 +157,22 @@ export class Main { ")", (clientId, clientSecret) => this.authService.logInApiKey(clientId, clientSecret) ); - this.apiKeyService = new ApiKeyService(this.tokenService, this.storageService); - this.userService = new UserService(this.tokenService, this.storageService); this.containerService = new ContainerService(this.cryptoService); + + this.organizationService = new OrganizationService(this.stateService); + this.keyConnectorService = new KeyConnectorService( - this.storageService, - this.userService, + this.stateService, this.cryptoService, this.apiService, this.tokenService, - this.logService + this.logService, + this.organizationService ); + this.authService = new AuthService( this.cryptoService, this.apiService, - this.userService, this.tokenService, this.appIdService, this.i18nService, @@ -165,84 +180,91 @@ export class Main { this.messagingService, null, this.logService, - this.apiKeyService, this.cryptoFunctionService, this.environmentService, - this.keyConnectorService - ); - this.configurationService = new ConfigurationService( - this.storageService, - this.secureStorageService, - process.env.BITWARDENCLI_CONNECTOR_PLAINTEXT_SECRETS !== "true" + this.keyConnectorService, + this.stateService ); + this.syncService = new SyncService( - this.configurationService, this.logService, this.cryptoFunctionService, this.apiService, this.messagingService, this.i18nService, - this.environmentService + this.environmentService, + this.stateService ); + + this.policyService = new PolicyService( + this.stateService, + this.organizationService, + this.apiService + ); + this.passwordGenerationService = new PasswordGenerationService( this.cryptoService, - this.storageService, - null + this.policyService, + this.stateService ); - this.policyService = new PolicyService(this.userService, this.storageService, this.apiService); - this.settingsService = new SettingsService(this.userService, this.storageService); + + this.settingsService = new SettingsService(this.stateService); + this.fileUploadService = new FileUploadService(this.logService, this.apiService); + this.cipherService = new CipherService( this.cryptoService, - this.userService, this.settingsService, this.apiService, this.fileUploadService, - this.storageService, this.i18nService, () => searchService, - this.logService - ); - this.searchService = new SearchService(this.cipherService, this.logService, this.i18nService); - this.folderService = new FolderService( - this.cryptoService, - this.userService, - this.apiService, - this.storageService, - this.i18nService, - this.cipherService - ); - this.collectionService = new CollectionService( - this.cryptoService, - this.userService, - this.storageService, - this.i18nService - ); - this.sendService = new SendService( - this.cryptoService, - this.userService, - this.apiService, - this.fileUploadService, - this.storageService, - this.i18nService, - this.cryptoFunctionService + this.logService, + this.stateService ); + this.searchService = new SearchService(this.cipherService, this.logService, this.i18nService); + + this.folderService = new FolderService( + this.cryptoService, + this.apiService, + this.i18nService, + this.cipherService, + this.stateService + ); + + this.collectionService = new CollectionService( + this.cryptoService, + this.i18nService, + this.stateService + ); + + this.sendService = new SendService( + this.cryptoService, + this.apiService, + this.fileUploadService, + this.i18nService, + this.cryptoFunctionService, + this.stateService + ); + + this.providerService = new ProviderService(this.stateService); + this.loginSyncService = new LoginSyncService( - this.userService, this.apiService, this.settingsService, this.folderService, this.cipherService, this.cryptoService, this.collectionService, - this.storageService, this.messagingService, this.policyService, this.sendService, this.logService, - this.tokenService, this.keyConnectorService, + this.stateService, + this.organizationService, + this.providerService, async (expired: boolean) => this.messagingService.send("logout", { expired: expired }) ); @@ -256,11 +278,12 @@ export class Main { async logout() { await this.tokenService.clearToken(); - await this.apiKeyService.clear(); + await this.stateService.clean(); } private async init() { await this.storageService.init(); + await this.stateService.init(); this.containerService.attachToWindow(global); await this.environmentService.setUrlsFromStorage(); // Dev Server URLs. Comment out the line above. @@ -269,16 +292,14 @@ export class Main { // api: 'http://localhost:4000', // identity: 'http://localhost:33656', // }); - const locale = await this.storageService.get(ConstantsService.localeKey); + const locale = await this.stateService.getLocale(); await this.i18nService.init(locale); this.authService.init(); - const installedVersion = await this.storageService.get( - ConstantsService.installedVersionKey - ); + const installedVersion = await this.stateService.getInstalledVersion(); const currentVersion = await this.platformUtilsService.getApplicationVersion(); if (installedVersion == null || installedVersion !== currentVersion) { - await this.storageService.save(ConstantsService.installedVersionKey, currentVersion); + await this.stateService.setInstalledVersion(currentVersion); } } } diff --git a/src/commands/clearCache.command.ts b/src/commands/clearCache.command.ts index 5831baaf..325c861c 100644 --- a/src/commands/clearCache.command.ts +++ b/src/commands/clearCache.command.ts @@ -2,20 +2,16 @@ import * as program from "commander"; import { I18nService } from "jslib-common/abstractions/i18n.service"; -import { ConfigurationService } from "../services/configuration.service"; - import { Response } from "jslib-node/cli/models/response"; import { MessageResponse } from "jslib-node/cli/models/response/messageResponse"; +import { StateService } from "../abstractions/state.service"; export class ClearCacheCommand { - constructor( - private configurationService: ConfigurationService, - private i18nService: I18nService - ) {} + constructor(private i18nService: I18nService, private stateService: StateService) {} async run(cmd: program.OptionValues): Promise { try { - await this.configurationService.clearStatefulSettings(true); + await this.stateService.clearSyncSettings(true); const res = new MessageResponse(this.i18nService.t("syncCacheCleared"), null); return Response.success(res); } catch (e) { diff --git a/src/commands/config.command.ts b/src/commands/config.command.ts index d50f4e29..e00dbf4b 100644 --- a/src/commands/config.command.ts +++ b/src/commands/config.command.ts @@ -3,7 +3,7 @@ import * as program from "commander"; import { EnvironmentService } from "jslib-common/abstractions/environment.service"; import { I18nService } from "jslib-common/abstractions/i18n.service"; -import { ConfigurationService } from "../services/configuration.service"; +import { StateService } from "../abstractions/state.service"; import { DirectoryType } from "../enums/directoryType"; @@ -33,7 +33,7 @@ export class ConfigCommand { constructor( private environmentService: EnvironmentService, private i18nService: I18nService, - private configurationService: ConfigurationService + private stateService: StateService ) {} async run(setting: string, value: string, options: program.OptionValues): Promise { @@ -126,35 +126,32 @@ export class ConfigCommand { } private async loadConfig() { - this.directory = await this.configurationService.getDirectoryType(); + this.directory = await this.stateService.getDirectoryType(); this.ldap = - (await this.configurationService.getDirectory(DirectoryType.Ldap)) || - this.ldap; + (await this.stateService.getDirectory(DirectoryType.Ldap)) || this.ldap; this.gsuite = - (await this.configurationService.getDirectory(DirectoryType.GSuite)) || + (await this.stateService.getDirectory(DirectoryType.GSuite)) || this.gsuite; this.azure = - (await this.configurationService.getDirectory( + (await this.stateService.getDirectory( DirectoryType.AzureActiveDirectory )) || this.azure; this.okta = - (await this.configurationService.getDirectory(DirectoryType.Okta)) || - this.okta; + (await this.stateService.getDirectory(DirectoryType.Okta)) || this.okta; this.oneLogin = - (await this.configurationService.getDirectory( - DirectoryType.OneLogin - )) || this.oneLogin; - this.sync = (await this.configurationService.getSync()) || this.sync; + (await this.stateService.getDirectory(DirectoryType.OneLogin)) || + this.oneLogin; + this.sync = (await this.stateService.getSync()) || this.sync; } private async saveConfig() { ConnectorUtils.adjustConfigForSave(this.ldap, this.sync); - await this.configurationService.saveDirectoryType(this.directory); - await this.configurationService.saveDirectory(DirectoryType.Ldap, this.ldap); - await this.configurationService.saveDirectory(DirectoryType.GSuite, this.gsuite); - await this.configurationService.saveDirectory(DirectoryType.AzureActiveDirectory, this.azure); - await this.configurationService.saveDirectory(DirectoryType.Okta, this.okta); - await this.configurationService.saveDirectory(DirectoryType.OneLogin, this.oneLogin); - await this.configurationService.saveSync(this.sync); + await this.stateService.setDirectoryType(this.directory); + await this.stateService.setDirectory(DirectoryType.Ldap, this.ldap); + await this.stateService.setDirectory(DirectoryType.GSuite, this.gsuite); + await this.stateService.setDirectory(DirectoryType.AzureActiveDirectory, this.azure); + await this.stateService.setDirectory(DirectoryType.Okta, this.okta); + await this.stateService.setDirectory(DirectoryType.OneLogin, this.oneLogin); + await this.stateService.setSync(this.sync); } } diff --git a/src/commands/lastSync.command.ts b/src/commands/lastSync.command.ts index ccccce5f..1a0fd82d 100644 --- a/src/commands/lastSync.command.ts +++ b/src/commands/lastSync.command.ts @@ -1,24 +1,24 @@ import * as program from "commander"; -import { ConfigurationService } from "../services/configuration.service"; +import { StateService } from "../abstractions/state.service"; import { Response } from "jslib-node/cli/models/response"; import { StringResponse } from "jslib-node/cli/models/response/stringResponse"; export class LastSyncCommand { - constructor(private configurationService: ConfigurationService) {} + constructor(private stateService: StateService) {} async run(object: string): Promise { try { switch (object.toLowerCase()) { case "groups": - const groupsDate = await this.configurationService.getLastGroupSyncDate(); + const groupsDate = await this.stateService.getLastGroupSync(); const groupsRes = new StringResponse( groupsDate == null ? null : groupsDate.toISOString() ); return Response.success(groupsRes); case "users": - const usersDate = await this.configurationService.getLastUserSyncDate(); + const usersDate = await this.stateService.getLastUserSync(); const usersRes = new StringResponse(usersDate == null ? null : usersDate.toISOString()); return Response.success(usersRes); default: diff --git a/src/main.ts b/src/main.ts index 33180008..19df477d 100644 --- a/src/main.ts +++ b/src/main.ts @@ -13,12 +13,15 @@ import { TrayMain } from "jslib-electron/tray.main"; import { UpdaterMain } from "jslib-electron/updater.main"; import { WindowMain } from "jslib-electron/window.main"; +import { StateService } from "./services/state.service"; + export class Main { logService: ElectronLogService; i18nService: I18nService; storageService: ElectronStorageService; messagingService: ElectronMainMessagingService; keytarStorageListener: KeytarStorageListener; + stateService: StateService; windowMain: WindowMain; messagingMain: MessagingMain; @@ -51,9 +54,10 @@ export class Main { this.logService = new ElectronLogService(null, app.getPath("userData")); this.i18nService = new I18nService("en", "./locales/"); this.storageService = new ElectronStorageService(app.getPath("userData")); + this.stateService = new StateService(this.storageService, null, this.logService, null); this.windowMain = new WindowMain( - this.storageService, + this.stateService, this.logService, false, 800, @@ -61,6 +65,7 @@ export class Main { (arg) => this.processDeepLink(arg), null ); + this.menuMain = new MenuMain(this); this.updaterMain = new UpdaterMain( this.i18nService, @@ -77,7 +82,9 @@ export class Main { }, "bitwardenDirectoryConnector" ); - this.trayMain = new TrayMain(this.windowMain, this.i18nService, this.storageService); + + this.trayMain = new TrayMain(this.windowMain, this.i18nService, this.stateService); + this.messagingMain = new MessagingMain( this.windowMain, this.menuMain, diff --git a/src/models/IConfiguration.ts b/src/models/IConfiguration.ts new file mode 100644 index 00000000..fc16444a --- /dev/null +++ b/src/models/IConfiguration.ts @@ -0,0 +1,2 @@ +// tslint:disable-next-line +export interface IConfiguration {} diff --git a/src/models/account.ts b/src/models/account.ts new file mode 100644 index 00000000..c63e750b --- /dev/null +++ b/src/models/account.ts @@ -0,0 +1,47 @@ +import { Account as BaseAccount } from "jslib-common/models/domain/account"; + +import { DirectoryType } from "src/enums/directoryType"; + +import { AzureConfiguration } from "./azureConfiguration"; +import { GSuiteConfiguration } from "./gsuiteConfiguration"; +import { LdapConfiguration } from "./ldapConfiguration"; +import { OktaConfiguration } from "./oktaConfiguration"; +import { OneLoginConfiguration } from "./oneLoginConfiguration"; +import { SyncConfiguration } from "./syncConfiguration"; + +export class Account extends BaseAccount { + directoryConfigurations?: DirectoryConfigurations = new DirectoryConfigurations(); + directorySettings: DirectorySettings = new DirectorySettings(); + clientKeys: ClientKeys = new ClientKeys(); + + constructor(init: Partial) { + super(init); + this.directoryConfigurations = init.directoryConfigurations ?? new DirectoryConfigurations(); + this.directorySettings = init.directorySettings ?? new DirectorySettings(); + } +} + +export class ClientKeys { + clientId: string; + clientSecret: string; +} + +export class DirectoryConfigurations { + ldap: LdapConfiguration; + gsuite: GSuiteConfiguration; + azure: AzureConfiguration; + okta: OktaConfiguration; + oneLogin: OneLoginConfiguration; +} + +export class DirectorySettings { + organizationId?: string; + sync?: SyncConfiguration; + directoryType?: DirectoryType; + userDelta?: string; + groupDelta?: string; + lastUserSync?: Date; + lastGroupSync?: Date; + lastSyncHash?: string; + syncingDir?: boolean; +} diff --git a/src/models/azureConfiguration.ts b/src/models/azureConfiguration.ts index 38c61338..0d980370 100644 --- a/src/models/azureConfiguration.ts +++ b/src/models/azureConfiguration.ts @@ -1,4 +1,6 @@ -export class AzureConfiguration { +import { IConfiguration } from "./IConfiguration"; + +export class AzureConfiguration implements IConfiguration { identityAuthority: string; tenant: string; applicationId: string; diff --git a/src/models/gsuiteConfiguration.ts b/src/models/gsuiteConfiguration.ts index 6d147993..5b5712b1 100644 --- a/src/models/gsuiteConfiguration.ts +++ b/src/models/gsuiteConfiguration.ts @@ -1,4 +1,6 @@ -export class GSuiteConfiguration { +import { IConfiguration } from "./IConfiguration"; + +export class GSuiteConfiguration implements IConfiguration { clientEmail: string; privateKey: string; domain: string; diff --git a/src/models/ldapConfiguration.ts b/src/models/ldapConfiguration.ts index 3a1cbf40..7c3ab41f 100644 --- a/src/models/ldapConfiguration.ts +++ b/src/models/ldapConfiguration.ts @@ -1,4 +1,6 @@ -export class LdapConfiguration { +import { IConfiguration } from "./IConfiguration"; + +export class LdapConfiguration implements IConfiguration { ssl = false; startTls = false; tlsCaPath: string; diff --git a/src/models/oktaConfiguration.ts b/src/models/oktaConfiguration.ts index 88f77f41..3ba3647f 100644 --- a/src/models/oktaConfiguration.ts +++ b/src/models/oktaConfiguration.ts @@ -1,4 +1,6 @@ -export class OktaConfiguration { +import { IConfiguration } from "./IConfiguration"; + +export class OktaConfiguration implements IConfiguration { orgUrl: string; token: string; } diff --git a/src/models/oneLoginConfiguration.ts b/src/models/oneLoginConfiguration.ts index 76d0f13a..ee1ecc9c 100644 --- a/src/models/oneLoginConfiguration.ts +++ b/src/models/oneLoginConfiguration.ts @@ -1,4 +1,6 @@ -export class OneLoginConfiguration { +import { IConfiguration } from "./IConfiguration"; + +export class OneLoginConfiguration implements IConfiguration { clientId: string; clientSecret: string; region = "us"; diff --git a/src/program.ts b/src/program.ts index 7e009360..484bb632 100644 --- a/src/program.ts +++ b/src/program.ts @@ -16,7 +16,6 @@ import { UpdateCommand } from "jslib-node/cli/commands/update.command"; import { BaseProgram } from "jslib-node/cli/baseProgram"; -import { ApiKeyService } from "jslib-common/abstractions/apiKey.service"; import { Response } from "jslib-node/cli/models/response"; import { StringResponse } from "jslib-node/cli/models/response/stringResponse"; @@ -32,11 +31,8 @@ const writeLn = (s: string, finalLine: boolean = false, error: boolean = false) }; export class Program extends BaseProgram { - private apiKeyService: ApiKeyService; - constructor(private main: Main) { - super(main.userService, writeLn); - this.apiKeyService = main.apiKeyService; + super(main.stateService, writeLn); } async run() { @@ -107,7 +103,7 @@ export class Program extends BaseProgram { this.main.passwordGenerationService, this.main.cryptoFunctionService, this.main.platformUtilsService, - this.main.userService, + this.main.stateService, this.main.cryptoService, this.main.policyService, "connector", @@ -197,7 +193,7 @@ export class Program extends BaseProgram { }) .action(async (object: string) => { await this.exitIfNotAuthed(); - const command = new LastSyncCommand(this.main.configurationService); + const command = new LastSyncCommand(this.main.stateService); const response = await command.run(object); this.processResponse(response); }); @@ -235,7 +231,7 @@ export class Program extends BaseProgram { const command = new ConfigCommand( this.main.environmentService, this.main.i18nService, - this.main.configurationService + this.main.stateService ); const response = await command.run(setting, value, options); this.processResponse(response); @@ -266,10 +262,7 @@ export class Program extends BaseProgram { writeLn("", true); }) .action(async (options: program.OptionValues) => { - const command = new ClearCacheCommand( - this.main.configurationService, - this.main.i18nService - ); + const command = new ClearCacheCommand(this.main.i18nService, this.main.stateService); const response = await command.run(options); this.processResponse(response); }); @@ -310,10 +303,10 @@ export class Program extends BaseProgram { } async exitIfAuthed() { - const authed = await this.apiKeyService.isAuthenticated(); + const authed = await this.stateService.getIsAuthenticated(); if (authed) { - const type = await this.apiKeyService.getEntityType(); - const id = await this.apiKeyService.getEntityId(); + const type = await this.stateService.getEntityType(); + const id = await this.stateService.getEntityId(); this.processResponse( Response.error("You are already logged in as " + type + "." + id + "."), true @@ -322,7 +315,7 @@ export class Program extends BaseProgram { } async exitIfNotAuthed() { - const authed = await this.apiKeyService.isAuthenticated(); + const authed = await this.stateService.getIsAuthenticated(); if (!authed) { this.processResponse(Response.error("You are not logged in."), true); } diff --git a/src/services/api.service.ts b/src/services/api.service.ts index f0342755..c9f8fd6d 100644 --- a/src/services/api.service.ts +++ b/src/services/api.service.ts @@ -1,15 +1,15 @@ -import { ApiKeyService } from "jslib-common/abstractions/apiKey.service"; import { AuthService } from "jslib-common/abstractions/auth.service"; import { EnvironmentService } from "jslib-common/abstractions/environment.service"; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; import { TokenService } from "jslib-common/abstractions/token.service"; +import { StateService } from "../abstractions/state.service"; import { ApiService as ApiServiceBase } from "jslib-common/services/api.service"; -export async function refreshToken(apiKeyService: ApiKeyService, authService: AuthService) { +export async function refreshToken(stateService: StateService, authService: AuthService) { try { - const clientId = await apiKeyService.getClientId(); - const clientSecret = await apiKeyService.getClientSecret(); + const clientId = await stateService.getApiKeyClientId(); + const clientSecret = await stateService.getApiKeyClientSecret(); if (clientId != null && clientSecret != null) { await authService.logInApiKey(clientId, clientSecret); } diff --git a/src/services/auth.service.ts b/src/services/auth.service.ts index 7748d9de..10c21931 100644 --- a/src/services/auth.service.ts +++ b/src/services/auth.service.ts @@ -1,5 +1,4 @@ import { ApiService } from "jslib-common/abstractions/api.service"; -import { ApiKeyService } from "jslib-common/abstractions/apiKey.service"; import { AppIdService } from "jslib-common/abstractions/appId.service"; import { CryptoService } from "jslib-common/abstractions/crypto.service"; import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service"; @@ -10,21 +9,25 @@ import { LogService } from "jslib-common/abstractions/log.service"; import { MessagingService } from "jslib-common/abstractions/messaging.service"; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; import { TokenService } from "jslib-common/abstractions/token.service"; -import { UserService } from "jslib-common/abstractions/user.service"; import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service"; +import { StateService } from "../abstractions/state.service"; import { AuthService as AuthServiceBase } from "jslib-common/services/auth.service"; +import { Account, DirectoryConfigurations, DirectorySettings } from "src/models/account"; + +import { AccountKeys, AccountProfile, AccountTokens } from "jslib-common/models/domain/account"; import { AuthResult } from "jslib-common/models/domain/authResult"; + import { DeviceRequest } from "jslib-common/models/request/deviceRequest"; import { TokenRequest } from "jslib-common/models/request/tokenRequest"; + import { IdentityTokenResponse } from "jslib-common/models/response/identityTokenResponse"; export class AuthService extends AuthServiceBase { constructor( cryptoService: CryptoService, apiService: ApiService, - userService: UserService, tokenService: TokenService, appIdService: AppIdService, i18nService: I18nService, @@ -32,15 +35,14 @@ export class AuthService extends AuthServiceBase { messagingService: MessagingService, vaultTimeoutService: VaultTimeoutService, logService: LogService, - private apiKeyService: ApiKeyService, cryptoFunctionService: CryptoFunctionService, environmentService: EnvironmentService, - keyConnectorService: KeyConnectorService + keyConnectorService: KeyConnectorService, + stateService: StateService ) { super( cryptoService, apiService, - userService, tokenService, appIdService, i18nService, @@ -49,8 +51,9 @@ export class AuthService extends AuthServiceBase { vaultTimeoutService, logService, cryptoFunctionService, - environmentService, keyConnectorService, + environmentService, + stateService, false ); } @@ -64,12 +67,13 @@ export class AuthService extends AuthServiceBase { } async logOut(callback: Function) { - this.apiKeyService.clear(); + this.stateService.clean(); super.logOut(callback); } private async organizationLogInHelper(clientId: string, clientSecret: string) { const appId = await this.appIdService.getAppId(); + const entityId = clientId.split("organization.")[1]; const deviceRequest = new DeviceRequest(appId, this.platformUtilsService); const request = new TokenRequest( null, @@ -88,9 +92,33 @@ export class AuthService extends AuthServiceBase { const tokenResponse = response as IdentityTokenResponse; result.resetMasterPassword = tokenResponse.resetMasterPassword; - await this.tokenService.setToken(tokenResponse.accessToken); - await this.apiKeyService.setInformation(clientId, clientSecret); - + await this.stateService.addAccount( + new Account({ + profile: { + ...new AccountProfile(), + ...{ + userId: entityId, + apiKeyClientId: clientId, + entityId: entityId, + }, + }, + tokens: { + ...new AccountTokens(), + ...{ + accessToken: tokenResponse.accessToken, + refreshToken: tokenResponse.refreshToken, + }, + }, + keys: { + ...new AccountKeys(), + ...{ + apiKeyClientSecret: clientSecret, + }, + }, + directorySettings: new DirectorySettings(), + directoryConfigurations: new DirectoryConfigurations(), + }) + ); return result; } } diff --git a/src/services/azure-directory.service.ts b/src/services/azure-directory.service.ts index 4e5ea2e8..0cd43ca2 100644 --- a/src/services/azure-directory.service.ts +++ b/src/services/azure-directory.service.ts @@ -11,11 +11,11 @@ import { SyncConfiguration } from "../models/syncConfiguration"; import { UserEntry } from "../models/userEntry"; import { BaseDirectoryService } from "./baseDirectory.service"; -import { ConfigurationService } from "./configuration.service"; import { IDirectoryService } from "./directory.service"; import { I18nService } from "jslib-common/abstractions/i18n.service"; import { LogService } from "jslib-common/abstractions/log.service"; +import { StateService } from "../abstractions/state.service"; const AzurePublicIdentityAuhtority = "login.microsoftonline.com"; const AzureGovermentIdentityAuhtority = "login.microsoftonline.us"; @@ -40,28 +40,28 @@ export class AzureDirectoryService extends BaseDirectoryService implements IDire private accessTokenExpiration: Date; constructor( - private configurationService: ConfigurationService, private logService: LogService, - private i18nService: I18nService + private i18nService: I18nService, + private stateService: StateService ) { super(); this.init(); } async getEntries(force: boolean, test: boolean): Promise<[GroupEntry[], UserEntry[]]> { - const type = await this.configurationService.getDirectoryType(); + const type = await this.stateService.getDirectoryType(); if (type !== DirectoryType.AzureActiveDirectory) { return; } - this.dirConfig = await this.configurationService.getDirectory( + this.dirConfig = await this.stateService.getDirectory( DirectoryType.AzureActiveDirectory ); if (this.dirConfig == null) { return; } - this.syncConfig = await this.configurationService.getSync(); + this.syncConfig = await this.stateService.getSync(); if (this.syncConfig == null) { return; } @@ -130,7 +130,7 @@ export class AzureDirectoryService extends BaseDirectoryService implements IDire const entries: UserEntry[] = []; let res: any = null; - const token = await this.configurationService.getUserDeltaToken(); + const token = await this.stateService.getUserDelta(); if (!force && token != null) { try { const deltaReq = this.client.api(token); @@ -168,7 +168,7 @@ export class AzureDirectoryService extends BaseDirectoryService implements IDire if (res[NextLink] == null) { if (res[DeltaLink] != null && saveDelta) { - await this.configurationService.saveUserDeltaToken(res[DeltaLink]); + await this.stateService.setUserDelta(res[DeltaLink]); } break; } else { diff --git a/src/services/configuration.service.ts b/src/services/configuration.service.ts deleted file mode 100644 index 73ce5478..00000000 --- a/src/services/configuration.service.ts +++ /dev/null @@ -1,238 +0,0 @@ -import { DirectoryType } from "../enums/directoryType"; - -import { StorageService } from "jslib-common/abstractions/storage.service"; -import { AzureConfiguration } from "../models/azureConfiguration"; -import { GSuiteConfiguration } from "../models/gsuiteConfiguration"; -import { LdapConfiguration } from "../models/ldapConfiguration"; -import { OktaConfiguration } from "../models/oktaConfiguration"; -import { OneLoginConfiguration } from "../models/oneLoginConfiguration"; -import { SyncConfiguration } from "../models/syncConfiguration"; - -const StoredSecurely = "[STORED SECURELY]"; -const Keys = { - ldap: "ldapPassword", - gsuite: "gsuitePrivateKey", - azure: "azureKey", - okta: "oktaToken", - oneLogin: "oneLoginClientSecret", - directoryConfigPrefix: "directoryConfig_", - sync: "syncConfig", - directoryType: "directoryType", - userDelta: "userDeltaToken", - groupDelta: "groupDeltaToken", - lastUserSync: "lastUserSync", - lastGroupSync: "lastGroupSync", - lastSyncHash: "lastSyncHash", - organizationId: "organizationId", -}; - -export class ConfigurationService { - constructor( - private storageService: StorageService, - private secureStorageService: StorageService, - private useSecureStorageForSecrets = true - ) {} - - async getDirectory(type: DirectoryType): Promise { - const config = await this.storageService.get(Keys.directoryConfigPrefix + type); - if (config == null) { - return config; - } - - if (this.useSecureStorageForSecrets) { - switch (type) { - case DirectoryType.Ldap: - (config as any).password = await this.secureStorageService.get(Keys.ldap); - break; - case DirectoryType.AzureActiveDirectory: - (config as any).key = await this.secureStorageService.get(Keys.azure); - break; - case DirectoryType.Okta: - (config as any).token = await this.secureStorageService.get(Keys.okta); - break; - case DirectoryType.GSuite: - (config as any).privateKey = await this.secureStorageService.get(Keys.gsuite); - break; - case DirectoryType.OneLogin: - (config as any).clientSecret = await this.secureStorageService.get(Keys.oneLogin); - break; - } - } - return config; - } - - async saveDirectory( - type: DirectoryType, - config: - | LdapConfiguration - | GSuiteConfiguration - | AzureConfiguration - | OktaConfiguration - | OneLoginConfiguration - ): Promise { - const savedConfig: any = Object.assign({}, config); - if (this.useSecureStorageForSecrets) { - switch (type) { - case DirectoryType.Ldap: - if (savedConfig.password == null) { - await this.secureStorageService.remove(Keys.ldap); - } else { - await this.secureStorageService.save(Keys.ldap, savedConfig.password); - savedConfig.password = StoredSecurely; - } - break; - case DirectoryType.AzureActiveDirectory: - if (savedConfig.key == null) { - await this.secureStorageService.remove(Keys.azure); - } else { - await this.secureStorageService.save(Keys.azure, savedConfig.key); - savedConfig.key = StoredSecurely; - } - break; - case DirectoryType.Okta: - if (savedConfig.token == null) { - await this.secureStorageService.remove(Keys.okta); - } else { - await this.secureStorageService.save(Keys.okta, savedConfig.token); - savedConfig.token = StoredSecurely; - } - break; - case DirectoryType.GSuite: - if (savedConfig.privateKey == null) { - await this.secureStorageService.remove(Keys.gsuite); - } else { - (config as GSuiteConfiguration).privateKey = savedConfig.privateKey = - savedConfig.privateKey.replace(/\\n/g, "\n"); - await this.secureStorageService.save(Keys.gsuite, savedConfig.privateKey); - savedConfig.privateKey = StoredSecurely; - } - break; - case DirectoryType.OneLogin: - if (savedConfig.clientSecret == null) { - await this.secureStorageService.remove(Keys.oneLogin); - } else { - await this.secureStorageService.save(Keys.oneLogin, savedConfig.clientSecret); - savedConfig.clientSecret = StoredSecurely; - } - break; - } - } - await this.storageService.save(Keys.directoryConfigPrefix + type, savedConfig); - } - - getSync(): Promise { - return this.storageService.get(Keys.sync); - } - - saveSync(config: SyncConfiguration) { - return this.storageService.save(Keys.sync, config); - } - - getDirectoryType(): Promise { - return this.storageService.get(Keys.directoryType); - } - - async saveDirectoryType(type: DirectoryType) { - const currentType = await this.getDirectoryType(); - if (type !== currentType) { - await this.clearStatefulSettings(); - } - - return this.storageService.save(Keys.directoryType, type); - } - - getUserDeltaToken(): Promise { - return this.storageService.get(Keys.userDelta); - } - - saveUserDeltaToken(token: string) { - if (token == null) { - return this.storageService.remove(Keys.userDelta); - } else { - return this.storageService.save(Keys.userDelta, token); - } - } - - getGroupDeltaToken(): Promise { - return this.storageService.get(Keys.groupDelta); - } - - saveGroupDeltaToken(token: string) { - if (token == null) { - return this.storageService.remove(Keys.groupDelta); - } else { - return this.storageService.save(Keys.groupDelta, token); - } - } - - async getLastUserSyncDate(): Promise { - const dateString = await this.storageService.get(Keys.lastUserSync); - if (dateString == null) { - return null; - } - return new Date(dateString); - } - - saveLastUserSyncDate(date: Date) { - if (date == null) { - return this.storageService.remove(Keys.lastUserSync); - } else { - return this.storageService.save(Keys.lastUserSync, date); - } - } - - async getLastGroupSyncDate(): Promise { - const dateString = await this.storageService.get(Keys.lastGroupSync); - if (dateString == null) { - return null; - } - return new Date(dateString); - } - - saveLastGroupSyncDate(date: Date) { - if (date == null) { - return this.storageService.remove(Keys.lastGroupSync); - } else { - return this.storageService.save(Keys.lastGroupSync, date); - } - } - - getLastSyncHash(): Promise { - return this.storageService.get(Keys.lastSyncHash); - } - - saveLastSyncHash(hash: string) { - if (hash == null) { - return this.storageService.remove(Keys.lastSyncHash); - } else { - return this.storageService.save(Keys.lastSyncHash, hash); - } - } - - getOrganizationId(): Promise { - return this.storageService.get(Keys.organizationId); - } - - async saveOrganizationId(id: string) { - const currentId = await this.getOrganizationId(); - if (currentId !== id) { - await this.clearStatefulSettings(); - } - - if (id == null) { - return this.storageService.remove(Keys.organizationId); - } else { - return this.storageService.save(Keys.organizationId, id); - } - } - - async clearStatefulSettings(hashToo = false) { - await this.saveUserDeltaToken(null); - await this.saveGroupDeltaToken(null); - await this.saveLastGroupSyncDate(null); - await this.saveLastUserSyncDate(null); - if (hashToo) { - await this.saveLastSyncHash(null); - } - } -} diff --git a/src/services/gsuite-directory.service.ts b/src/services/gsuite-directory.service.ts index ec053618..112942b2 100644 --- a/src/services/gsuite-directory.service.ts +++ b/src/services/gsuite-directory.service.ts @@ -9,11 +9,11 @@ import { SyncConfiguration } from "../models/syncConfiguration"; import { UserEntry } from "../models/userEntry"; import { BaseDirectoryService } from "./baseDirectory.service"; -import { ConfigurationService } from "./configuration.service"; import { IDirectoryService } from "./directory.service"; import { I18nService } from "jslib-common/abstractions/i18n.service"; import { LogService } from "jslib-common/abstractions/log.service"; +import { StateService } from "../abstractions/state.service"; export class GSuiteDirectoryService extends BaseDirectoryService implements IDirectoryService { private client: JWT; @@ -23,28 +23,28 @@ export class GSuiteDirectoryService extends BaseDirectoryService implements IDir private syncConfig: SyncConfiguration; constructor( - private configurationService: ConfigurationService, private logService: LogService, - private i18nService: I18nService + private i18nService: I18nService, + private stateService: StateService ) { super(); this.service = google.admin("directory_v1"); } async getEntries(force: boolean, test: boolean): Promise<[GroupEntry[], UserEntry[]]> { - const type = await this.configurationService.getDirectoryType(); + const type = await this.stateService.getDirectoryType(); if (type !== DirectoryType.GSuite) { return; } - this.dirConfig = await this.configurationService.getDirectory( + this.dirConfig = await this.stateService.getDirectory( DirectoryType.GSuite ); if (this.dirConfig == null) { return; } - this.syncConfig = await this.configurationService.getSync(); + this.syncConfig = await this.stateService.getSync(); if (this.syncConfig == null) { return; } diff --git a/src/services/ldap-directory.service.ts b/src/services/ldap-directory.service.ts index 1b1f7638..061cc837 100644 --- a/src/services/ldap-directory.service.ts +++ b/src/services/ldap-directory.service.ts @@ -10,11 +10,11 @@ import { LdapConfiguration } from "../models/ldapConfiguration"; import { SyncConfiguration } from "../models/syncConfiguration"; import { UserEntry } from "../models/userEntry"; -import { ConfigurationService } from "./configuration.service"; import { IDirectoryService } from "./directory.service"; import { I18nService } from "jslib-common/abstractions/i18n.service"; import { LogService } from "jslib-common/abstractions/log.service"; +import { StateService } from "../abstractions/state.service"; import { Utils } from "jslib-common/misc/utils"; @@ -26,25 +26,23 @@ export class LdapDirectoryService implements IDirectoryService { private syncConfig: SyncConfiguration; constructor( - private configurationService: ConfigurationService, private logService: LogService, - private i18nService: I18nService + private i18nService: I18nService, + private stateService: StateService ) {} async getEntries(force: boolean, test: boolean): Promise<[GroupEntry[], UserEntry[]]> { - const type = await this.configurationService.getDirectoryType(); + const type = await this.stateService.getDirectoryType(); if (type !== DirectoryType.Ldap) { return; } - this.dirConfig = await this.configurationService.getDirectory( - DirectoryType.Ldap - ); + this.dirConfig = await this.stateService.getDirectory(DirectoryType.Ldap); if (this.dirConfig == null) { return; } - this.syncConfig = await this.configurationService.getSync(); + this.syncConfig = await this.stateService.getSync(); if (this.syncConfig == null) { return; } @@ -71,7 +69,7 @@ export class LdapDirectoryService implements IDirectoryService { } private async getUsers(force: boolean): Promise { - const lastSync = await this.configurationService.getLastUserSyncDate(); + const lastSync = await this.stateService.getLastUserSync(); let filter = this.buildBaseFilter(this.syncConfig.userObjectClass, this.syncConfig.userFilter); filter = this.buildRevisionFilter(filter, force, lastSync); @@ -147,7 +145,7 @@ export class LdapDirectoryService implements IDirectoryService { private async getGroups(force: boolean): Promise { const entries: GroupEntry[] = []; - const lastSync = await this.configurationService.getLastUserSyncDate(); + const lastSync = await this.stateService.getLastUserSync(); const originalFilter = this.buildBaseFilter( this.syncConfig.groupObjectClass, this.syncConfig.groupFilter diff --git a/src/services/okta-directory.service.ts b/src/services/okta-directory.service.ts index 11fb22bb..7e2f1e34 100644 --- a/src/services/okta-directory.service.ts +++ b/src/services/okta-directory.service.ts @@ -6,13 +6,13 @@ import { SyncConfiguration } from "../models/syncConfiguration"; import { UserEntry } from "../models/userEntry"; import { BaseDirectoryService } from "./baseDirectory.service"; -import { ConfigurationService } from "./configuration.service"; import { IDirectoryService } from "./directory.service"; import { I18nService } from "jslib-common/abstractions/i18n.service"; import { LogService } from "jslib-common/abstractions/log.service"; import * as https from "https"; +import { StateService } from "../abstractions/state.service"; const DelayBetweenBuildGroupCallsInMilliseconds = 500; @@ -22,27 +22,25 @@ export class OktaDirectoryService extends BaseDirectoryService implements IDirec private lastBuildGroupCall: number; constructor( - private configurationService: ConfigurationService, private logService: LogService, - private i18nService: I18nService + private i18nService: I18nService, + private stateService: StateService ) { super(); } async getEntries(force: boolean, test: boolean): Promise<[GroupEntry[], UserEntry[]]> { - const type = await this.configurationService.getDirectoryType(); + const type = await this.stateService.getDirectoryType(); if (type !== DirectoryType.Okta) { return; } - this.dirConfig = await this.configurationService.getDirectory( - DirectoryType.Okta - ); + this.dirConfig = await this.stateService.getDirectory(DirectoryType.Okta); if (this.dirConfig == null) { return; } - this.syncConfig = await this.configurationService.getSync(); + this.syncConfig = await this.stateService.getSync(); if (this.syncConfig == null) { return; } @@ -68,7 +66,7 @@ export class OktaDirectoryService extends BaseDirectoryService implements IDirec private async getUsers(force: boolean): Promise { const entries: UserEntry[] = []; - const lastSync = await this.configurationService.getLastUserSyncDate(); + const lastSync = await this.stateService.getLastUserSync(); const oktaFilter = this.buildOktaFilter(this.syncConfig.userFilter, force, lastSync); const setFilter = this.createCustomSet(this.syncConfig.userFilter); @@ -124,7 +122,7 @@ export class OktaDirectoryService extends BaseDirectoryService implements IDirec setFilter: [boolean, Set] ): Promise { const entries: GroupEntry[] = []; - const lastSync = await this.configurationService.getLastGroupSyncDate(); + const lastSync = await this.stateService.getLastGroupSync(); const oktaFilter = this.buildOktaFilter(this.syncConfig.groupFilter, force, lastSync); this.logService.info("Querying groups."); diff --git a/src/services/onelogin-directory.service.ts b/src/services/onelogin-directory.service.ts index 4838fc35..753e069a 100644 --- a/src/services/onelogin-directory.service.ts +++ b/src/services/onelogin-directory.service.ts @@ -6,11 +6,11 @@ import { SyncConfiguration } from "../models/syncConfiguration"; import { UserEntry } from "../models/userEntry"; import { BaseDirectoryService } from "./baseDirectory.service"; -import { ConfigurationService } from "./configuration.service"; import { IDirectoryService } from "./directory.service"; import { I18nService } from "jslib-common/abstractions/i18n.service"; import { LogService } from "jslib-common/abstractions/log.service"; +import { StateService } from "../abstractions/state.service"; // Basic email validation: something@something.something const ValidEmailRegex = /^\S+@\S+\.\S+$/; @@ -22,27 +22,27 @@ export class OneLoginDirectoryService extends BaseDirectoryService implements ID private allUsers: any[] = []; constructor( - private configurationService: ConfigurationService, private logService: LogService, - private i18nService: I18nService + private i18nService: I18nService, + private stateService: StateService ) { super(); } async getEntries(force: boolean, test: boolean): Promise<[GroupEntry[], UserEntry[]]> { - const type = await this.configurationService.getDirectoryType(); + const type = await this.stateService.getDirectoryType(); if (type !== DirectoryType.OneLogin) { return; } - this.dirConfig = await this.configurationService.getDirectory( + this.dirConfig = await this.stateService.getDirectory( DirectoryType.OneLogin ); if (this.dirConfig == null) { return; } - this.syncConfig = await this.configurationService.getSync(); + this.syncConfig = await this.stateService.getSync(); if (this.syncConfig == null) { return; } diff --git a/src/services/state.service.ts b/src/services/state.service.ts new file mode 100644 index 00000000..5ee30ef4 --- /dev/null +++ b/src/services/state.service.ts @@ -0,0 +1,535 @@ +import { StateService as BaseStateService } from "jslib-common/services/state.service"; + +import { State } from "jslib-common/models/domain/state"; +import { StorageOptions } from "jslib-common/models/domain/storageOptions"; + +import { Account } from "src/models/account"; +import { AzureConfiguration } from "src/models/azureConfiguration"; +import { GSuiteConfiguration } from "src/models/gsuiteConfiguration"; +import { IConfiguration } from "src/models/IConfiguration"; +import { LdapConfiguration } from "src/models/ldapConfiguration"; +import { OktaConfiguration } from "src/models/oktaConfiguration"; +import { OneLoginConfiguration } from "src/models/oneLoginConfiguration"; + +import { LogService } from "jslib-common/abstractions/log.service"; +import { StorageService } from "jslib-common/abstractions/storage.service"; +import { StateService as StateServiceAbstraction } from "src/abstractions/state.service"; +import { DirectoryType } from "src/enums/directoryType"; +import { SyncConfiguration } from "src/models/syncConfiguration"; +import { StateMigrationService } from "./stateMigration.service"; + +const SecureStorageKeys = { + ldap: "ldapPassword", + gsuite: "gsuitePrivateKey", + azure: "azureKey", + okta: "oktaToken", + oneLogin: "oneLoginClientSecret", + userDelta: "userDeltaToken", + groupDelta: "groupDeltaToken", + lastUserSync: "lastUserSync", + lastGroupSync: "lastGroupSync", + lastSyncHash: "lastSyncHash", +}; + +const StoredSecurely = "[STORED SECURELY]"; + +export class StateService extends BaseStateService implements StateServiceAbstraction { + constructor( + protected storageService: StorageService, + protected secureStorageService: StorageService, + protected logService: LogService, + protected stateMigrationService: StateMigrationService, + private useSecureStorageForSecrets = true + ) { + super(storageService, secureStorageService, logService, stateMigrationService); + } + + async getDirectory(type: DirectoryType): Promise { + const config = await this.getConfiguration(type); + if (config == null) { + return config as T; + } + + if (this.useSecureStorageForSecrets) { + switch (type) { + case DirectoryType.Ldap: + (config as any).password = await this.getLdapKey(); + break; + case DirectoryType.AzureActiveDirectory: + (config as any).key = await this.getAzureKey(); + break; + case DirectoryType.Okta: + (config as any).token = await this.getOktaKey(); + break; + case DirectoryType.GSuite: + (config as any).privateKey = await this.getGsuiteKey(); + break; + case DirectoryType.OneLogin: + (config as any).clientSecret = await this.getOneLoginKey(); + break; + } + } + return config as T; + } + + async setDirectory( + type: DirectoryType, + config: + | LdapConfiguration + | GSuiteConfiguration + | AzureConfiguration + | OktaConfiguration + | OneLoginConfiguration + ): Promise { + const savedConfig: any = Object.assign({}, config); + if (this.useSecureStorageForSecrets) { + switch (type) { + case DirectoryType.Ldap: + await this.setLdapKey(savedConfig.password); + savedConfig.password = StoredSecurely; + await this.setLdapConfiguration(savedConfig); + break; + case DirectoryType.AzureActiveDirectory: + await this.setAzureKey(savedConfig.key); + savedConfig.key = StoredSecurely; + await this.setAzureConfiguration(savedConfig); + break; + case DirectoryType.Okta: + await this.setOktaKey(savedConfig.token); + savedConfig.token = StoredSecurely; + await this.setOktaConfiguration(savedConfig); + break; + case DirectoryType.GSuite: + if (savedConfig.privateKey == null) { + await this.setGsuiteKey(null); + } else { + (config as GSuiteConfiguration).privateKey = savedConfig.privateKey = + savedConfig.privateKey.replace(/\\n/g, "\n"); + await this.setGsuiteKey(savedConfig.privateKey); + savedConfig.privateKey = StoredSecurely; + } + await this.setGsuiteConfiguration(savedConfig); + break; + case DirectoryType.OneLogin: + await this.setOneLoginKey(savedConfig.clientSecret); + savedConfig.clientSecret = StoredSecurely; + await this.setOneLoginConfiguration(savedConfig); + break; + } + } + } + + async getLdapKey(options?: StorageOptions): Promise { + options = this.reconcileOptions(options, await this.defaultSecureStorageOptions()); + if (options?.userId == null) { + return null; + } + return await this.secureStorageService.get( + `${options.userId}_${SecureStorageKeys.ldap}` + ); + } + + async setLdapKey(value: string, options?: StorageOptions): Promise { + options = this.reconcileOptions(options, await this.defaultSecureStorageOptions()); + if (options?.userId == null) { + return; + } + await this.secureStorageService.save( + `${options.userId}_${SecureStorageKeys.ldap}`, + value, + options + ); + } + + async getGsuiteKey(options?: StorageOptions): Promise { + options = this.reconcileOptions(options, await this.defaultSecureStorageOptions()); + if (options?.userId == null) { + return null; + } + return await this.secureStorageService.get( + `${options.userId}_${SecureStorageKeys.gsuite}` + ); + } + + async setGsuiteKey(value: string, options?: StorageOptions): Promise { + options = this.reconcileOptions(options, await this.defaultSecureStorageOptions()); + if (options?.userId == null) { + return; + } + await this.secureStorageService.save( + `${options.userId}_${SecureStorageKeys.gsuite}`, + value, + options + ); + } + + async getAzureKey(options?: StorageOptions): Promise { + options = this.reconcileOptions(options, await this.defaultSecureStorageOptions()); + if (options?.userId == null) { + return null; + } + return await this.secureStorageService.get( + `${options.userId}_${SecureStorageKeys.azure}` + ); + } + + async setAzureKey(value: string, options?: StorageOptions): Promise { + options = this.reconcileOptions(options, await this.defaultSecureStorageOptions()); + if (options?.userId == null) { + return; + } + await this.secureStorageService.save( + `${options.userId}_${SecureStorageKeys.azure}`, + value, + options + ); + } + + async getOktaKey(options?: StorageOptions): Promise { + options = this.reconcileOptions(options, await this.defaultSecureStorageOptions()); + if (options?.userId == null) { + return null; + } + return await this.secureStorageService.get( + `${options.userId}_${SecureStorageKeys.okta}` + ); + } + + async setOktaKey(value: string, options?: StorageOptions): Promise { + options = this.reconcileOptions(options, await this.defaultSecureStorageOptions()); + if (options?.userId == null) { + return; + } + await this.secureStorageService.save( + `${options.userId}_${SecureStorageKeys.okta}`, + value, + options + ); + } + + async getOneLoginKey(options?: StorageOptions): Promise { + options = this.reconcileOptions(options, await this.defaultSecureStorageOptions()); + if (options?.userId == null) { + return null; + } + return await this.secureStorageService.get( + `${options.userId}_${SecureStorageKeys.oneLogin}` + ); + } + + async setOneLoginKey(value: string, options?: StorageOptions): Promise { + options = this.reconcileOptions(options, await this.defaultSecureStorageOptions()); + if (options?.userId == null) { + return; + } + await this.secureStorageService.save( + `${options.userId}_${SecureStorageKeys.oneLogin}`, + value, + options + ); + } + + async getUserDelta(options?: StorageOptions): Promise { + options = this.reconcileOptions(options, await this.defaultSecureStorageOptions()); + if (options?.userId == null) { + return null; + } + return await this.secureStorageService.get( + `${options.userId}_${SecureStorageKeys.userDelta}` + ); + } + + async setUserDelta(value: string, options?: StorageOptions): Promise { + options = this.reconcileOptions(options, await this.defaultSecureStorageOptions()); + if (options?.userId == null) { + return; + } + await this.secureStorageService.save( + `${options.userId}_${SecureStorageKeys.userDelta}`, + value, + options + ); + } + + async getGroupDelta(options?: StorageOptions): Promise { + options = this.reconcileOptions(options, await this.defaultSecureStorageOptions()); + if (options?.userId == null) { + return null; + } + return await this.secureStorageService.get( + `${options.userId}_${SecureStorageKeys.groupDelta}` + ); + } + + async setGroupDelta(value: string, options?: StorageOptions): Promise { + options = this.reconcileOptions(options, await this.defaultSecureStorageOptions()); + if (options?.userId == null) { + return; + } + await this.secureStorageService.save( + `${options.userId}_${SecureStorageKeys.groupDelta}`, + value, + options + ); + } + + async getConfiguration(type: DirectoryType): Promise { + switch (type) { + case DirectoryType.Ldap: + return await this.getLdapConfiguration(); + case DirectoryType.GSuite: + return await this.getGsuiteConfiguration(); + case DirectoryType.AzureActiveDirectory: + return await this.getAzureConfiguration(); + case DirectoryType.Okta: + return await this.getOktaConfiguration(); + case DirectoryType.OneLogin: + return await this.getOneLoginConfiguration(); + } + } + + async getLdapConfiguration(options?: StorageOptions): Promise { + return ( + await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) + )?.directoryConfigurations?.ldap; + } + + async setLdapConfiguration(value: LdapConfiguration, options?: StorageOptions): Promise { + const account = await this.getAccount( + this.reconcileOptions(options, await this.defaultOnDiskOptions()) + ); + account.directoryConfigurations.ldap = value; + await this.saveAccount( + account, + this.reconcileOptions(options, await this.defaultOnDiskOptions()) + ); + } + + async getGsuiteConfiguration(options?: StorageOptions): Promise { + return ( + await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) + )?.directoryConfigurations?.gsuite; + } + + async setGsuiteConfiguration( + value: GSuiteConfiguration, + options?: StorageOptions + ): Promise { + const account = await this.getAccount( + this.reconcileOptions(options, await this.defaultOnDiskOptions()) + ); + account.directoryConfigurations.gsuite = value; + await this.saveAccount( + account, + this.reconcileOptions(options, await this.defaultOnDiskOptions()) + ); + } + + async getAzureConfiguration(options?: StorageOptions): Promise { + return ( + await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) + )?.directoryConfigurations?.azure; + } + + async setAzureConfiguration(value: AzureConfiguration, options?: StorageOptions): Promise { + const account = await this.getAccount( + this.reconcileOptions(options, await this.defaultOnDiskOptions()) + ); + account.directoryConfigurations.azure = value; + await this.saveAccount( + account, + this.reconcileOptions(options, await this.defaultOnDiskOptions()) + ); + } + + async getOktaConfiguration(options?: StorageOptions): Promise { + return ( + await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) + )?.directoryConfigurations?.okta; + } + + async setOktaConfiguration(value: OktaConfiguration, options?: StorageOptions): Promise { + const account = await this.getAccount( + this.reconcileOptions(options, await this.defaultOnDiskOptions()) + ); + account.directoryConfigurations.okta = value; + await this.saveAccount( + account, + this.reconcileOptions(options, await this.defaultOnDiskOptions()) + ); + } + + async getOneLoginConfiguration(options?: StorageOptions): Promise { + return ( + await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) + )?.directoryConfigurations?.oneLogin; + } + + async setOneLoginConfiguration( + value: OneLoginConfiguration, + options?: StorageOptions + ): Promise { + const account = await this.getAccount( + this.reconcileOptions(options, await this.defaultOnDiskOptions()) + ); + account.directoryConfigurations.oneLogin = value; + await this.saveAccount( + account, + this.reconcileOptions(options, await this.defaultOnDiskOptions()) + ); + } + + async getOrganizationId(options?: StorageOptions): Promise { + return ( + await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) + )?.directorySettings?.organizationId; + } + + async setOrganizationId(value: string, options?: StorageOptions): Promise { + const currentId = await this.getOrganizationId(); + if (currentId !== value) { + await this.clearSyncSettings(); + } + + const account = await this.getAccount( + this.reconcileOptions(options, await this.defaultOnDiskOptions()) + ); + account.directorySettings.organizationId = value; + await this.saveAccount( + account, + this.reconcileOptions(options, await this.defaultOnDiskOptions()) + ); + } + + async getSync(options?: StorageOptions): Promise { + return ( + await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) + )?.directorySettings?.sync; + } + + async setSync(value: SyncConfiguration, options?: StorageOptions): Promise { + const account = await this.getAccount( + this.reconcileOptions(options, await this.defaultOnDiskOptions()) + ); + account.directorySettings.sync = value; + await this.saveAccount( + account, + this.reconcileOptions(options, await this.defaultOnDiskOptions()) + ); + } + + async getDirectoryType(options?: StorageOptions): Promise { + return ( + await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) + )?.directorySettings?.directoryType; + } + + async setDirectoryType(value: DirectoryType, options?: StorageOptions): Promise { + const currentType = await this.getDirectoryType(); + if (value !== currentType) { + await this.clearSyncSettings(); + } + const account = await this.getAccount( + this.reconcileOptions(options, await this.defaultOnDiskOptions()) + ); + account.directorySettings.directoryType = value; + await this.saveAccount( + account, + this.reconcileOptions(options, await this.defaultOnDiskOptions()) + ); + } + + async getLastUserSync(options?: StorageOptions): Promise { + return ( + await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) + )?.directorySettings?.lastUserSync; + } + + async setLastUserSync(value: Date, options?: StorageOptions): Promise { + const account = await this.getAccount( + this.reconcileOptions(options, await this.defaultOnDiskOptions()) + ); + account.directorySettings.lastUserSync = value; + await this.saveAccount( + account, + this.reconcileOptions(options, await this.defaultOnDiskOptions()) + ); + } + + async getLastGroupSync(options?: StorageOptions): Promise { + return ( + await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) + )?.directorySettings?.lastGroupSync; + } + + async setLastGroupSync(value: Date, options?: StorageOptions): Promise { + const account = await this.getAccount( + this.reconcileOptions(options, await this.defaultOnDiskOptions()) + ); + account.directorySettings.lastGroupSync = value; + await this.saveAccount( + account, + this.reconcileOptions(options, await this.defaultOnDiskOptions()) + ); + } + + async getLastSyncHash(options?: StorageOptions): Promise { + return ( + await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) + )?.directorySettings?.lastSyncHash; + } + + async setLastSyncHash(value: string, options?: StorageOptions): Promise { + const account = await this.getAccount( + this.reconcileOptions(options, await this.defaultOnDiskOptions()) + ); + account.directorySettings.lastSyncHash = value; + await this.saveAccount( + account, + this.reconcileOptions(options, await this.defaultOnDiskOptions()) + ); + } + + async getSyncingDir(options?: StorageOptions): Promise { + return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions))) + ?.directorySettings?.syncingDir; + } + + async setSyncingDir(value: boolean, options?: StorageOptions): Promise { + const account = await this.getAccount( + this.reconcileOptions(options, this.defaultInMemoryOptions) + ); + account.directorySettings.syncingDir = value; + await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions)); + } + + async clearSyncSettings(hashToo = false) { + await this.setUserDelta(null); + await this.setGroupDelta(null); + await this.setLastGroupSync(null); + await this.setLastUserSync(null); + if (hashToo) { + await this.setLastSyncHash(null); + } + } + + protected async scaffoldNewAccountStorage(account: Account): Promise { + await this.scaffoldNewAccountDiskStorage(account); + } + + protected async scaffoldNewAccountDiskStorage(account: Account): Promise { + const storedState = + (await this.storageService.get>( + "state", + await this.defaultOnDiskLocalOptions() + )) ?? new State(); + const storedAccount = storedState.accounts[account.profile.userId]; + if (storedAccount != null) { + account.settings = storedAccount.settings; + account.directorySettings = storedAccount.directorySettings; + account.directoryConfigurations = storedAccount.directoryConfigurations; + } + storedState.accounts[account.profile.userId] = account; + await this.saveStateToStorage(storedState, await this.defaultOnDiskLocalOptions()); + } +} diff --git a/src/services/stateMigration.service.ts b/src/services/stateMigration.service.ts new file mode 100644 index 00000000..8c4ca3f9 --- /dev/null +++ b/src/services/stateMigration.service.ts @@ -0,0 +1,170 @@ +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 { DirectoryType } from "src/enums/directoryType"; + +import { Account, DirectoryConfigurations, DirectorySettings } from "src/models/account"; +import { AzureConfiguration } from "src/models/azureConfiguration"; +import { GSuiteConfiguration } from "src/models/gsuiteConfiguration"; +import { LdapConfiguration } from "src/models/ldapConfiguration"; +import { OktaConfiguration } from "src/models/oktaConfiguration"; +import { OneLoginConfiguration } from "src/models/oneLoginConfiguration"; +import { SyncConfiguration } from "src/models/syncConfiguration"; + +const SecureStorageKeys: { [key: string]: any } = { + ldap: "ldapPassword", + gsuite: "gsuitePrivateKey", + azure: "azureKey", + okta: "oktaToken", + oneLogin: "oneLoginClientSecret", + directoryConfigPrefix: "directoryConfig_", + sync: "syncConfig", + directoryType: "directoryType", + userDelta: "userDeltaToken", + groupDelta: "groupDeltaToken", + organizationId: "organizationId", +}; + +const Keys: { [key: string]: any } = { + state: "state", + entityId: "entityId", + directoryType: "directoryType", + organizationId: "organizationId", + lastUserSync: "lastUserSync", + lastGroupSync: "lastGroupSync", + lastSyncHash: "lastSyncHash", + syncingDir: "syncingDir", + syncConfig: "syncConfig", +}; + +const ClientKeys: { [key: string]: any } = { + clientIdOld: "clientId", + clientId: "apikey_clientId", + clientSecretOld: "clientSecret", + clientSecret: "apikey_clientSecret", +}; + +export class StateMigrationService extends BaseStateMigrationService { + async needsMigration(): Promise { + const currentStateVersion = ( + await this.storageService.get>("state", { + htmlStorageLocation: HtmlStorageLocation.Local, + }) + )?.globals?.stateVersion; + return currentStateVersion == null || currentStateVersion < this.latestVersion; + } + + async migrate(): Promise { + let currentStateVersion = + (await this.storageService.get>("state"))?.globals?.stateVersion ?? 1; + while (currentStateVersion < this.latestVersion) { + switch (currentStateVersion) { + case 1: + await this.migrateClientKeys(); + await this.migrateStateFrom1To2(); + break; + } + currentStateVersion += 1; + } + } + + // TODO: remove this migration when we are confident existing api keys are all migrated. Probably 1-2 releases. + protected async migrateClientKeys() { + const oldClientId = await this.storageService.get(ClientKeys.clientIdOld); + const oldClientSecret = await this.storageService.get(ClientKeys.clientSecretOld); + + if (oldClientId != null) { + await this.storageService.save(ClientKeys.clientId, oldClientId); + await this.storageService.remove(ClientKeys.clientIdOld); + } + + if (oldClientSecret != null) { + await this.storageService.save(ClientKeys.clientSecret, oldClientSecret); + await this.storageService.remove(ClientKeys.clientSecretOld); + } + } + + protected async migrateStateFrom1To2(useSecureStorageForSecrets: boolean = true): Promise { + await super.migrateStateFrom1To2(); + const state = await this.storageService.get>(Keys.state); + const userId = await this.storageService.get(Keys.entityId); + + if (userId != null) { + state.accounts[userId] = new Account({ + directorySettings: { + directoryType: await this.storageService.get(Keys.directoryType), + organizationId: await this.storageService.get(Keys.organizationId), + lastUserSync: await this.storageService.get(Keys.lastUserSync), + lastGroupSync: await this.storageService.get(Keys.lastGroupSync), + lastSyncHash: await this.storageService.get(Keys.lastSyncHash), + syncingDir: await this.storageService.get(Keys.syncingDir), + sync: await this.storageService.get(Keys.syncConfig), + }, + profile: { + entityId: await this.storageService.get(Keys.entityId), + }, + directoryConfigurations: new DirectoryConfigurations(), + clientKeys: { + clientId: await this.storageService.get(ClientKeys.clientId), + clientSecret: await this.storageService.get(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( + SecureStorageKeys.directoryConfigPrefix + key + ); + break; + case DirectoryType.GSuite: + state.accounts[userId].directoryConfigurations.gsuite = + await this.storageService.get( + SecureStorageKeys.directoryConfigPrefix + key + ); + break; + case DirectoryType.AzureActiveDirectory: + state.accounts[userId].directoryConfigurations.azure = + await this.storageService.get( + SecureStorageKeys.directoryConfigPrefix + key + ); + break; + case DirectoryType.Okta: + state.accounts[userId].directoryConfigurations.okta = + await this.storageService.get( + SecureStorageKeys.directoryConfigPrefix + key + ); + break; + case DirectoryType.OneLogin: + state.accounts[userId].directoryConfigurations.oneLogin = + await this.storageService.get( + SecureStorageKeys.directoryConfigPrefix + key + ); + break; + } + await this.storageService.remove(SecureStorageKeys.directoryConfigPrefix + key); + } + } + + state.globals.environmentUrls = await this.storageService.get("environmentUrls"); + + await this.storageService.save("state", state); + + if (useSecureStorageForSecrets) { + for (const key in SecureStorageKeys) { + if (await this.secureStorageService.has(SecureStorageKeys[key])) { + await this.secureStorageService.save( + `${userId}_${SecureStorageKeys[key]}`, + await this.secureStorageService.get(SecureStorageKeys[key]) + ); + await this.secureStorageService.remove(SecureStorageKeys[key]); + } + } + } + } +} diff --git a/src/services/sync.service.ts b/src/services/sync.service.ts index 0fede353..948956cc 100644 --- a/src/services/sync.service.ts +++ b/src/services/sync.service.ts @@ -15,8 +15,8 @@ import { MessagingService } from "jslib-common/abstractions/messaging.service"; import { Utils } from "jslib-common/misc/utils"; +import { StateService } from "../abstractions/state.service"; import { AzureDirectoryService } from "./azure-directory.service"; -import { ConfigurationService } from "./configuration.service"; import { IDirectoryService } from "./directory.service"; import { GSuiteDirectoryService } from "./gsuite-directory.service"; import { LdapDirectoryService } from "./ldap-directory.service"; @@ -27,17 +27,17 @@ export class SyncService { private dirType: DirectoryType; constructor( - private configurationService: ConfigurationService, private logService: LogService, private cryptoFunctionService: CryptoFunctionService, private apiService: ApiService, private messagingService: MessagingService, private i18nService: I18nService, - private environmentService: EnvironmentService + private environmentService: EnvironmentService, + private stateService: StateService ) {} async sync(force: boolean, test: boolean): Promise<[GroupEntry[], UserEntry[]]> { - this.dirType = await this.configurationService.getDirectoryType(); + this.dirType = await this.stateService.getDirectoryType(); if (this.dirType == null) { throw new Error("No directory configured."); } @@ -47,9 +47,9 @@ export class SyncService { throw new Error("Cannot load directory service."); } - const syncConfig = await this.configurationService.getSync(); - const startingGroupDelta = await this.configurationService.getGroupDeltaToken(); - const startingUserDelta = await this.configurationService.getUserDeltaToken(); + const syncConfig = await this.stateService.getSync(); + const startingGroupDelta = await this.stateService.getGroupDelta(); + const startingUserDelta = await this.stateService.getUserDelta(); const now = new Date(); this.messagingService.send("dirSyncStarted"); @@ -90,7 +90,7 @@ export class SyncService { ); const reqJson = JSON.stringify(req); - const orgId = await this.configurationService.getOrganizationId(); + const orgId = await this.stateService.getOrganizationId(); if (orgId == null) { throw new Error("Organization not set."); } @@ -112,11 +112,11 @@ export class SyncService { if (hashBuff != null) { hash = Utils.fromBufferToB64(hashBuff); } - const lastHash = await this.configurationService.getLastSyncHash(); + const lastHash = await this.stateService.getLastSyncHash(); if (lastHash == null || (hash !== lastHash && hashLegacy !== lastHash)) { await this.apiService.postPublicImportDirectory(req); - await this.configurationService.saveLastSyncHash(hash); + await this.stateService.setLastSyncHash(hash); } else { groups = null; users = null; @@ -127,8 +127,8 @@ export class SyncService { return [groups, users]; } catch (e) { if (!test) { - await this.configurationService.saveGroupDeltaToken(startingGroupDelta); - await this.configurationService.saveUserDeltaToken(startingUserDelta); + await this.stateService.setGroupDelta(startingGroupDelta); + await this.stateService.setUserDelta(startingUserDelta); } this.messagingService.send("dirSyncCompleted", { successfully: false }); @@ -200,35 +200,15 @@ export class SyncService { private getDirectoryService(): IDirectoryService { switch (this.dirType) { case DirectoryType.GSuite: - return new GSuiteDirectoryService( - this.configurationService, - this.logService, - this.i18nService - ); + return new GSuiteDirectoryService(this.logService, this.i18nService, this.stateService); case DirectoryType.AzureActiveDirectory: - return new AzureDirectoryService( - this.configurationService, - this.logService, - this.i18nService - ); + return new AzureDirectoryService(this.logService, this.i18nService, this.stateService); case DirectoryType.Ldap: - return new LdapDirectoryService( - this.configurationService, - this.logService, - this.i18nService - ); + return new LdapDirectoryService(this.logService, this.i18nService, this.stateService); case DirectoryType.Okta: - return new OktaDirectoryService( - this.configurationService, - this.logService, - this.i18nService - ); + return new OktaDirectoryService(this.logService, this.i18nService, this.stateService); case DirectoryType.OneLogin: - return new OneLoginDirectoryService( - this.configurationService, - this.logService, - this.i18nService - ); + return new OneLoginDirectoryService(this.logService, this.i18nService, this.stateService); default: return null; } @@ -263,10 +243,10 @@ export class SyncService { private async saveSyncTimes(syncConfig: SyncConfiguration, time: Date) { if (syncConfig.groups) { - await this.configurationService.saveLastGroupSyncDate(time); + await this.stateService.setLastGroupSync(time); } if (syncConfig.users) { - await this.configurationService.saveLastUserSyncDate(time); + await this.stateService.setLastUserSync(time); } } }