1
0
mirror of https://github.com/bitwarden/directory-connector synced 2025-12-11 13:53:22 +00:00

[refactor] Implement StateService (#192)

* [refactor(Account Switching)] Implement StateService

* [bug] Migration service updates

* [bug] Fix organizationId coming in as null

* [bug] Use correct storage location

* [bug] Fix secure storage issues

* [bug] Small fixes

* [bug] lint fixes

* [bug] Undo comment

* [bug] Make method names match super

* update jslib

* Add index signature to keys

* Run prettier

* Start dbus

* Start dbus a different way

* Update build.yml

* Add eval

* Init keyring as well

* Remove eval

* Add eval's back

* Remove unused import

* Remove unnecessary null checks

* Change userId to be entityId instead of clientId

* Remove config service

* lint fixes

* Add clientKeys to account

Co-authored-by: Robyn MacCallum <robyntmaccallum@gmail.com>
This commit is contained in:
Addison Beck
2021-12-22 15:16:23 -05:00
committed by GitHub
parent a893c78c74
commit d2ba7631b5
35 changed files with 1155 additions and 581 deletions

View File

@@ -98,7 +98,11 @@ jobs:
- name: Version Test - name: Version Test
run: | 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 mkdir -p test/linux
unzip ./dist-cli/bwdc-linux-$_PACKAGE_VERSION.zip -d ./test/linux unzip ./dist-cli/bwdc-linux-$_PACKAGE_VERSION.zip -d ./test/linux

2
jslib

Submodule jslib updated: 8fc3cf50d2...9e26336549

View File

@@ -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<Account> {
getDirectory: <IConfiguration>(type: DirectoryType) => Promise<IConfiguration>;
setDirectory: (
type: DirectoryType,
config:
| LdapConfiguration
| GSuiteConfiguration
| AzureConfiguration
| OktaConfiguration
| OneLoginConfiguration
) => Promise<any>;
getLdapKey: (options?: StorageOptions) => Promise<string>;
setLdapKey: (value: string, options?: StorageOptions) => Promise<void>;
getGsuiteKey: (options?: StorageOptions) => Promise<string>;
setGsuiteKey: (value: string, options?: StorageOptions) => Promise<void>;
getAzureKey: (options?: StorageOptions) => Promise<string>;
setAzureKey: (value: string, options?: StorageOptions) => Promise<void>;
getOktaKey: (options?: StorageOptions) => Promise<string>;
setOktaKey: (value: string, options?: StorageOptions) => Promise<void>;
getOneLoginKey: (options?: StorageOptions) => Promise<string>;
setOneLoginKey: (value: string, options?: StorageOptions) => Promise<void>;
getLdapConfiguration: (options?: StorageOptions) => Promise<LdapConfiguration>;
setLdapConfiguration: (value: LdapConfiguration, options?: StorageOptions) => Promise<void>;
getGsuiteConfiguration: (options?: StorageOptions) => Promise<GSuiteConfiguration>;
setGsuiteConfiguration: (value: GSuiteConfiguration, options?: StorageOptions) => Promise<void>;
getAzureConfiguration: (options?: StorageOptions) => Promise<AzureConfiguration>;
setAzureConfiguration: (value: AzureConfiguration, options?: StorageOptions) => Promise<void>;
getOktaConfiguration: (options?: StorageOptions) => Promise<OktaConfiguration>;
setOktaConfiguration: (value: OktaConfiguration, options?: StorageOptions) => Promise<void>;
getOneLoginConfiguration: (options?: StorageOptions) => Promise<OneLoginConfiguration>;
setOneLoginConfiguration: (
value: OneLoginConfiguration,
options?: StorageOptions
) => Promise<void>;
getOrganizationId: (options?: StorageOptions) => Promise<string>;
setOrganizationId: (value: string, options?: StorageOptions) => Promise<void>;
getSync: (options?: StorageOptions) => Promise<SyncConfiguration>;
setSync: (value: SyncConfiguration, options?: StorageOptions) => Promise<void>;
getDirectoryType: (options?: StorageOptions) => Promise<DirectoryType>;
setDirectoryType: (value: DirectoryType, options?: StorageOptions) => Promise<void>;
getUserDelta: (options?: StorageOptions) => Promise<string>;
setUserDelta: (value: string, options?: StorageOptions) => Promise<void>;
getLastUserSync: (options?: StorageOptions) => Promise<Date>;
setLastUserSync: (value: Date, options?: StorageOptions) => Promise<void>;
getLastGroupSync: (options?: StorageOptions) => Promise<Date>;
setLastGroupSync: (value: Date, options?: StorageOptions) => Promise<void>;
getGroupDelta: (options?: StorageOptions) => Promise<string>;
setGroupDelta: (value: string, options?: StorageOptions) => Promise<void>;
getLastSyncHash: (options?: StorageOptions) => Promise<string>;
setLastSyncHash: (value: string, options?: StorageOptions) => Promise<void>;
getSyncingDir: (options?: StorageOptions) => Promise<boolean>;
setSyncingDir: (value: boolean, options?: StorageOptions) => Promise<void>;
clearSyncSettings: (syncHashToo: boolean) => Promise<void>;
}

View File

@@ -1,24 +1,19 @@
import { import { Component, Input, ViewChild, ViewContainerRef } from "@angular/core";
Component,
ComponentFactoryResolver,
Input,
ViewChild,
ViewContainerRef,
} from "@angular/core";
import { Router } from "@angular/router"; import { Router } from "@angular/router";
import { EnvironmentComponent } from "./environment.component"; import { EnvironmentComponent } from "./environment.component";
import { ApiKeyService } from "jslib-common/abstractions/apiKey.service";
import { AuthService } from "jslib-common/abstractions/auth.service"; import { AuthService } from "jslib-common/abstractions/auth.service";
import { I18nService } from "jslib-common/abstractions/i18n.service"; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service"; import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "../../abstractions/state.service";
import { ModalService } from "jslib-angular/services/modal.service"; import { ModalService } from "jslib-angular/services/modal.service";
import { HtmlStorageLocation } from "jslib-common/enums/htmlStorageLocation";
import { Utils } from "jslib-common/misc/utils"; import { Utils } from "jslib-common/misc/utils";
import { ConfigurationService } from "../../services/configuration.service";
@Component({ @Component({
selector: "app-apiKey", selector: "app-apiKey",
@@ -36,14 +31,12 @@ export class ApiKeyComponent {
constructor( constructor(
private authService: AuthService, private authService: AuthService,
private apiKeyService: ApiKeyService,
private router: Router, private router: Router,
private i18nService: I18nService, private i18nService: I18nService,
private componentFactoryResolver: ComponentFactoryResolver,
private configurationService: ConfigurationService,
private platformUtilsService: PlatformUtilsService, private platformUtilsService: PlatformUtilsService,
private modalService: ModalService, private modalService: ModalService,
private logService: LogService private logService: LogService,
private stateService: StateService
) {} ) {}
async submit() { async submit() {
@@ -85,8 +78,8 @@ export class ApiKeyComponent {
try { try {
this.formPromise = this.authService.logInApiKey(this.clientId, this.clientSecret); this.formPromise = this.authService.logInApiKey(this.clientId, this.clientSecret);
await this.formPromise; await this.formPromise;
const organizationId = await this.apiKeyService.getEntityId(); const organizationId = await this.stateService.getEntityId();
await this.configurationService.saveOrganizationId(organizationId); await this.stateService.setOrganizationId(organizationId);
this.router.navigate([this.successRoute]); this.router.navigate([this.successRoute]);
} catch (e) { } catch (e) {
this.logService.error(e); this.logService.error(e);

View File

@@ -16,13 +16,12 @@ import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service"; import { LogService } from "jslib-common/abstractions/log.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service"; import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.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 { 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 { SyncService } from "../services/sync.service";
import { StateService } from "../abstractions/state.service";
const BroadcasterSubscriptionId = "AppComponent"; const BroadcasterSubscriptionId = "AppComponent";
@Component({ @Component({
@@ -36,7 +35,6 @@ export class AppComponent implements OnInit {
constructor( constructor(
private broadcasterService: BroadcasterService, private broadcasterService: BroadcasterService,
private userService: UserService,
private tokenService: TokenService, private tokenService: TokenService,
private authService: AuthService, private authService: AuthService,
private router: Router, private router: Router,
@@ -46,7 +44,6 @@ export class AppComponent implements OnInit {
private ngZone: NgZone, private ngZone: NgZone,
private platformUtilsService: PlatformUtilsService, private platformUtilsService: PlatformUtilsService,
private messagingService: MessagingService, private messagingService: MessagingService,
private configurationService: ConfigurationService,
private syncService: SyncService, private syncService: SyncService,
private stateService: StateService, private stateService: StateService,
private logService: LogService private logService: LogService
@@ -58,21 +55,21 @@ export class AppComponent implements OnInit {
switch (message.command) { switch (message.command) {
case "syncScheduleStarted": case "syncScheduleStarted":
case "syncScheduleStopped": case "syncScheduleStopped":
this.stateService.save("syncingDir", message.command === "syncScheduleStarted"); this.stateService.setSyncingDir(message.command === "syncScheduleStarted");
break; break;
case "logout": case "logout":
this.logOut(!!message.expired); this.logOut(!!message.expired);
break; break;
case "checkDirSync": case "checkDirSync":
try { try {
const syncConfig = await this.configurationService.getSync(); const syncConfig = await this.stateService.getSync();
if (syncConfig.interval == null || syncConfig.interval < 5) { if (syncConfig.interval == null || syncConfig.interval < 5) {
return; return;
} }
const syncInterval = syncConfig.interval * 60000; const syncInterval = syncConfig.interval * 60000;
const lastGroupSync = await this.configurationService.getLastGroupSyncDate(); const lastGroupSync = await this.stateService.getLastGroupSync();
const lastUserSync = await this.configurationService.getLastUserSyncDate(); const lastUserSync = await this.stateService.getLastUserSync();
let lastSync: Date = null; let lastSync: Date = null;
if (lastGroupSync != null && lastUserSync == null) { if (lastGroupSync != null && lastUserSync == null) {
lastSync = lastGroupSync; lastSync = lastGroupSync;
@@ -119,10 +116,8 @@ export class AppComponent implements OnInit {
} }
private async logOut(expired: boolean) { private async logOut(expired: boolean) {
const userId = await this.userService.getUserId();
await this.tokenService.clearToken(); await this.tokenService.clearToken();
await this.userService.clear(); await this.stateService.clean();
this.authService.logOut(async () => { this.authService.logOut(async () => {
if (expired) { if (expired) {

View File

@@ -1,19 +1,16 @@
import { Injectable } from "@angular/core"; import { Injectable } from "@angular/core";
import { CanActivate, Router } from "@angular/router"; import { CanActivate } from "@angular/router";
import { ApiKeyService } from "jslib-common/abstractions/apiKey.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service"; import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { StateService } from "../../abstractions/state.service";
@Injectable() @Injectable()
export class AuthGuardService implements CanActivate { export class AuthGuardService implements CanActivate {
constructor( constructor(private stateService: StateService, private messagingService: MessagingService) {}
private apiKeyService: ApiKeyService,
private router: Router,
private messagingService: MessagingService
) {}
async canActivate() { async canActivate() {
const isAuthed = await this.apiKeyService.isAuthenticated(); const isAuthed = await this.stateService.getIsAuthenticated();
if (!isAuthed) { if (!isAuthed) {
this.messagingService.send("logout"); this.messagingService.send("logout");
return false; return false;

View File

@@ -1,14 +1,14 @@
import { Injectable } from "@angular/core"; import { Injectable } from "@angular/core";
import { CanActivate, Router } from "@angular/router"; import { CanActivate, Router } from "@angular/router";
import { ApiKeyService } from "jslib-common/abstractions/apiKey.service"; import { StateService } from "../../abstractions/state.service";
@Injectable() @Injectable()
export class LaunchGuardService implements CanActivate { export class LaunchGuardService implements CanActivate {
constructor(private apiKeyService: ApiKeyService, private router: Router) {} constructor(private stateService: StateService, private router: Router) {}
async canActivate() { async canActivate() {
const isAuthed = await this.apiKeyService.isAuthenticated(); const isAuthed = await this.stateService.getIsAuthenticated();
if (!isAuthed) { if (!isAuthed) {
return true; return true;
} }

View File

@@ -9,23 +9,16 @@ import { ElectronRendererStorageService } from "jslib-electron/services/electron
import { AuthGuardService } from "./auth-guard.service"; import { AuthGuardService } from "./auth-guard.service";
import { LaunchGuardService } from "./launch-guard.service"; import { LaunchGuardService } from "./launch-guard.service";
import { ConfigurationService } from "../../services/configuration.service";
import { I18nService } from "../../services/i18n.service"; import { I18nService } from "../../services/i18n.service";
import { SyncService } from "../../services/sync.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 { 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 { ContainerService } from "jslib-common/services/container.service";
import { NodeCryptoFunctionService } from "jslib-node/services/nodeCryptoFunction.service"; import { NodeCryptoFunctionService } from "jslib-node/services/nodeCryptoFunction.service";
import { ApiService as ApiServiceAbstraction } from "jslib-common/abstractions/api.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 { AppIdService as AppIdServiceAbstraction } from "jslib-common/abstractions/appId.service";
import { AuthService as AuthServiceAbstraction } from "jslib-common/abstractions/auth.service"; import { AuthService as AuthServiceAbstraction } from "jslib-common/abstractions/auth.service";
import { BroadcasterService as BroadcasterServiceAbstraction } from "jslib-common/abstractions/broadcaster.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 { KeyConnectorService as KeyConnectorServiceAbstraction } from "jslib-common/abstractions/keyConnector.service";
import { LogService as LogServiceAbstraction } from "jslib-common/abstractions/log.service"; import { LogService as LogServiceAbstraction } from "jslib-common/abstractions/log.service";
import { MessagingService as MessagingServiceAbstraction } from "jslib-common/abstractions/messaging.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 { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService as PolicyServiceAbstraction } from "jslib-common/abstractions/policy.service"; import { StateMigrationService as StateMigrationServiceAbstraction } from "jslib-common/abstractions/stateMigration.service";
import { StateService as StateServiceAbstraction } from "jslib-common/abstractions/state.service";
import { StorageService as StorageServiceAbstraction } from "jslib-common/abstractions/storage.service"; import { StorageService as StorageServiceAbstraction } from "jslib-common/abstractions/storage.service";
import { TokenService as TokenServiceAbstraction } from "jslib-common/abstractions/token.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 { 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 { ApiService, refreshToken } from "../../services/api.service";
import { AuthService } from "../../services/auth.service"; import { AuthService } from "../../services/auth.service";
import { StateService } from "../../services/state.service";
import { StateMigrationService } from "../../services/stateMigration.service";
function refreshTokenCallback(injector: Injector) { function refreshTokenCallback(injector: Injector) {
return () => { return () => {
const apiKeyService = injector.get(ApiKeyServiceAbstraction); const stateService = injector.get(StateServiceAbstraction);
const authService = injector.get(AuthServiceAbstraction); const authService = injector.get(AuthServiceAbstraction);
return refreshToken(apiKeyService, authService); return refreshToken(stateService, authService);
}; };
} }
@@ -61,13 +55,11 @@ export function initFactory(
i18nService: I18nService, i18nService: I18nService,
authService: AuthService, authService: AuthService,
platformUtilsService: PlatformUtilsServiceAbstraction, platformUtilsService: PlatformUtilsServiceAbstraction,
storageService: StorageServiceAbstraction,
userService: UserServiceAbstraction,
apiService: ApiServiceAbstraction,
stateService: StateServiceAbstraction, stateService: StateServiceAbstraction,
cryptoService: CryptoServiceAbstraction cryptoService: CryptoServiceAbstraction
): Function { ): Function {
return async () => { return async () => {
await stateService.init();
await environmentService.setUrlsFromStorage(); await environmentService.setUrlsFromStorage();
await i18nService.init(); await i18nService.init();
authService.init(); authService.init();
@@ -77,7 +69,7 @@ export function initFactory(
window.document.title = i18nService.t("bitwardenDirectoryConnector"); window.document.title = i18nService.t("bitwardenDirectoryConnector");
let installAction = null; let installAction = null;
const installedVersion = await storageService.get<string>(ConstantsService.installedVersionKey); const installedVersion = await stateService.getInstalledVersion();
const currentVersion = await platformUtilsService.getApplicationVersion(); const currentVersion = await platformUtilsService.getApplicationVersion();
if (installedVersion == null) { if (installedVersion == null) {
installAction = "install"; installAction = "install";
@@ -86,16 +78,9 @@ export function initFactory(
} }
if (installAction != null) { 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); const containerService = new ContainerService(cryptoService);
containerService.attachToWindow(window); containerService.attachToWindow(window);
}; };
@@ -113,9 +98,6 @@ export function initFactory(
I18nServiceAbstraction, I18nServiceAbstraction,
AuthServiceAbstraction, AuthServiceAbstraction,
PlatformUtilsServiceAbstraction, PlatformUtilsServiceAbstraction,
StorageServiceAbstraction,
UserServiceAbstraction,
ApiServiceAbstraction,
StateServiceAbstraction, StateServiceAbstraction,
CryptoServiceAbstraction, CryptoServiceAbstraction,
], ],
@@ -139,15 +121,11 @@ export function initFactory(
useFactory: ( useFactory: (
i18nService: I18nServiceAbstraction, i18nService: I18nServiceAbstraction,
messagingService: MessagingServiceAbstraction, messagingService: MessagingServiceAbstraction,
storageService: StorageServiceAbstraction stateService: StateServiceAbstraction
) => new ElectronPlatformUtilsService(i18nService, messagingService, true, storageService), ) => new ElectronPlatformUtilsService(i18nService, messagingService, true, stateService),
deps: [I18nServiceAbstraction, MessagingServiceAbstraction, StorageServiceAbstraction], deps: [I18nServiceAbstraction, MessagingServiceAbstraction, StateServiceAbstraction],
},
{
provide: CryptoFunctionServiceAbstraction,
useClass: NodeCryptoFunctionService,
deps: [],
}, },
{ provide: CryptoFunctionServiceAbstraction, useClass: NodeCryptoFunctionService, deps: [] },
{ {
provide: ApiServiceAbstraction, provide: ApiServiceAbstraction,
useFactory: ( useFactory: (
@@ -172,18 +150,12 @@ export function initFactory(
Injector, Injector,
], ],
}, },
{
provide: ApiKeyServiceAbstraction,
useClass: ApiKeyService,
deps: [TokenServiceAbstraction, StorageServiceAbstraction],
},
{ {
provide: AuthServiceAbstraction, provide: AuthServiceAbstraction,
useClass: AuthService, useClass: AuthService,
deps: [ deps: [
CryptoServiceAbstraction, CryptoServiceAbstraction,
ApiServiceAbstraction, ApiServiceAbstraction,
UserServiceAbstraction,
TokenServiceAbstraction, TokenServiceAbstraction,
AppIdServiceAbstraction, AppIdServiceAbstraction,
I18nServiceAbstraction, I18nServiceAbstraction,
@@ -191,32 +163,42 @@ export function initFactory(
MessagingServiceAbstraction, MessagingServiceAbstraction,
VaultTimeoutServiceAbstraction, VaultTimeoutServiceAbstraction,
LogServiceAbstraction, LogServiceAbstraction,
ApiKeyServiceAbstraction,
CryptoFunctionServiceAbstraction, CryptoFunctionServiceAbstraction,
EnvironmentServiceAbstraction, EnvironmentServiceAbstraction,
KeyConnectorServiceAbstraction, KeyConnectorServiceAbstraction,
StateServiceAbstraction,
], ],
}, },
{
provide: ConfigurationService,
useClass: ConfigurationService,
deps: [StorageServiceAbstraction, "SECURE_STORAGE"],
},
{ {
provide: SyncService, provide: SyncService,
useClass: SyncService, useClass: SyncService,
deps: [ deps: [
ConfigurationService,
LogServiceAbstraction, LogServiceAbstraction,
CryptoFunctionServiceAbstraction, CryptoFunctionServiceAbstraction,
ApiServiceAbstraction, ApiServiceAbstraction,
MessagingServiceAbstraction, MessagingServiceAbstraction,
I18nServiceAbstraction, I18nServiceAbstraction,
EnvironmentServiceAbstraction, EnvironmentServiceAbstraction,
StateServiceAbstraction,
], ],
}, },
AuthGuardService, AuthGuardService,
LaunchGuardService, LaunchGuardService,
{
provide: StateMigrationServiceAbstraction,
useClass: StateMigrationService,
deps: [StorageServiceAbstraction, "SECURE_STORAGE"],
},
{
provide: StateServiceAbstraction,
useClass: StateService,
deps: [
StorageServiceAbstraction,
"SECURE_STORAGE",
LogServiceAbstraction,
StateMigrationServiceAbstraction,
],
},
], ],
}) })
export class ServicesModule {} export class ServicesModule {}

View File

@@ -4,17 +4,17 @@ import { BroadcasterService } from "jslib-common/abstractions/broadcaster.servic
import { I18nService } from "jslib-common/abstractions/i18n.service"; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service"; import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { SyncService } from "../../services/sync.service"; import { SyncService } from "../../services/sync.service";
import { GroupEntry } from "../../models/groupEntry"; import { GroupEntry } from "../../models/groupEntry";
import { SimResult } from "../../models/simResult"; import { SimResult } from "../../models/simResult";
import { UserEntry } from "../../models/userEntry"; import { UserEntry } from "../../models/userEntry";
import { ConfigurationService } from "../../services/configuration.service";
import { ConnectorUtils } from "../../utils"; import { ConnectorUtils } from "../../utils";
import { StateService } from "../../abstractions/state.service";
const BroadcasterSubscriptionId = "DashboardComponent"; const BroadcasterSubscriptionId = "DashboardComponent";
@Component({ @Component({
@@ -38,7 +38,6 @@ export class DashboardComponent implements OnInit, OnDestroy {
constructor( constructor(
private i18nService: I18nService, private i18nService: I18nService,
private syncService: SyncService, private syncService: SyncService,
private configurationService: ConfigurationService,
private broadcasterService: BroadcasterService, private broadcasterService: BroadcasterService,
private ngZone: NgZone, private ngZone: NgZone,
private messagingService: MessagingService, 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(); this.updateLastSync();
} }
@@ -122,7 +121,7 @@ export class DashboardComponent implements OnInit, OnDestroy {
} }
private async updateLastSync() { private async updateLastSync() {
this.lastGroupSync = await this.configurationService.getLastGroupSyncDate(); this.lastGroupSync = await this.stateService.getLastGroupSync();
this.lastUserSync = await this.configurationService.getLastUserSyncDate(); this.lastUserSync = await this.stateService.getLastUserSync();
} }
} }

View File

@@ -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 { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
import { I18nService } from "jslib-common/abstractions/i18n.service"; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service"; import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { ConfigurationService } from "../../services/configuration.service"; import { StateService } from "../../abstractions/state.service";
const BroadcasterSubscriptionId = "MoreComponent"; const BroadcasterSubscriptionId = "MoreComponent";
@@ -22,10 +22,10 @@ export class MoreComponent implements OnInit {
private platformUtilsService: PlatformUtilsService, private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService, private i18nService: I18nService,
private messagingService: MessagingService, private messagingService: MessagingService,
private configurationService: ConfigurationService,
private broadcasterService: BroadcasterService, private broadcasterService: BroadcasterService,
private ngZone: NgZone, private ngZone: NgZone,
private changeDetectorRef: ChangeDetectorRef private changeDetectorRef: ChangeDetectorRef,
private stateService: StateService
) {} ) {}
async ngOnInit() { async ngOnInit() {
@@ -71,7 +71,7 @@ export class MoreComponent implements OnInit {
} }
async clearCache() { async clearCache() {
await this.configurationService.clearStatefulSettings(true); await this.stateService.clearSyncSettings(true);
this.platformUtilsService.showToast("success", null, this.i18nService.t("syncCacheCleared")); this.platformUtilsService.showToast("success", null, this.i18nService.t("syncCacheCleared"));
} }
} }

View File

@@ -2,11 +2,6 @@ import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit } from "@angula
import { I18nService } from "jslib-common/abstractions/i18n.service"; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.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"; import { DirectoryType } from "../../enums/directoryType";
@@ -17,6 +12,7 @@ import { OktaConfiguration } from "../../models/oktaConfiguration";
import { OneLoginConfiguration } from "../../models/oneLoginConfiguration"; import { OneLoginConfiguration } from "../../models/oneLoginConfiguration";
import { SyncConfiguration } from "../../models/syncConfiguration"; import { SyncConfiguration } from "../../models/syncConfiguration";
import { StateService } from "../../abstractions/state.service";
import { ConnectorUtils } from "../../utils"; import { ConnectorUtils } from "../../utils";
@Component({ @Component({
@@ -40,14 +36,13 @@ export class SettingsComponent implements OnInit, OnDestroy {
constructor( constructor(
private i18nService: I18nService, private i18nService: I18nService,
private configurationService: ConfigurationService,
private changeDetectorRef: ChangeDetectorRef, private changeDetectorRef: ChangeDetectorRef,
private ngZone: NgZone, private ngZone: NgZone,
private stateService: StateService, private logService: LogService,
private logService: LogService private stateService: StateService
) { ) {
this.directoryOptions = [ this.directoryOptions = [
{ name: i18nService.t("select"), value: null }, { name: this.i18nService.t("select"), value: null },
{ name: "Active Directory / LDAP", value: DirectoryType.Ldap }, { name: "Active Directory / LDAP", value: DirectoryType.Ldap },
{ name: "Azure Active Directory", value: DirectoryType.AzureActiveDirectory }, { name: "Azure Active Directory", value: DirectoryType.AzureActiveDirectory },
{ name: "G Suite (Google)", value: DirectoryType.GSuite }, { name: "G Suite (Google)", value: DirectoryType.GSuite },
@@ -57,25 +52,22 @@ export class SettingsComponent implements OnInit, OnDestroy {
} }
async ngOnInit() { async ngOnInit() {
this.directory = await this.configurationService.getDirectoryType(); this.directory = await this.stateService.getDirectoryType();
this.ldap = this.ldap =
(await this.configurationService.getDirectory<LdapConfiguration>(DirectoryType.Ldap)) || (await this.stateService.getDirectory<LdapConfiguration>(DirectoryType.Ldap)) || this.ldap;
this.ldap;
this.gsuite = this.gsuite =
(await this.configurationService.getDirectory<GSuiteConfiguration>(DirectoryType.GSuite)) || (await this.stateService.getDirectory<GSuiteConfiguration>(DirectoryType.GSuite)) ||
this.gsuite; this.gsuite;
this.azure = this.azure =
(await this.configurationService.getDirectory<AzureConfiguration>( (await this.stateService.getDirectory<AzureConfiguration>(
DirectoryType.AzureActiveDirectory DirectoryType.AzureActiveDirectory
)) || this.azure; )) || this.azure;
this.okta = this.okta =
(await this.configurationService.getDirectory<OktaConfiguration>(DirectoryType.Okta)) || (await this.stateService.getDirectory<OktaConfiguration>(DirectoryType.Okta)) || this.okta;
this.okta;
this.oneLogin = this.oneLogin =
(await this.configurationService.getDirectory<OneLoginConfiguration>( (await this.stateService.getDirectory<OneLoginConfiguration>(DirectoryType.OneLogin)) ||
DirectoryType.OneLogin this.oneLogin;
)) || this.oneLogin; this.sync = (await this.stateService.getSync()) || this.sync;
this.sync = (await this.configurationService.getSync()) || this.sync;
} }
async ngOnDestroy() { async ngOnDestroy() {
@@ -87,13 +79,13 @@ export class SettingsComponent implements OnInit, OnDestroy {
if (this.ldap != null && this.ldap.ad) { if (this.ldap != null && this.ldap.ad) {
this.ldap.pagedSearch = true; this.ldap.pagedSearch = true;
} }
await this.configurationService.saveDirectoryType(this.directory); await this.stateService.setDirectoryType(this.directory);
await this.configurationService.saveDirectory(DirectoryType.Ldap, this.ldap); await this.stateService.setDirectory(DirectoryType.Ldap, this.ldap);
await this.configurationService.saveDirectory(DirectoryType.GSuite, this.gsuite); await this.stateService.setDirectory(DirectoryType.GSuite, this.gsuite);
await this.configurationService.saveDirectory(DirectoryType.AzureActiveDirectory, this.azure); await this.stateService.setDirectory(DirectoryType.AzureActiveDirectory, this.azure);
await this.configurationService.saveDirectory(DirectoryType.Okta, this.okta); await this.stateService.setDirectory(DirectoryType.Okta, this.okta);
await this.configurationService.saveDirectory(DirectoryType.OneLogin, this.oneLogin); await this.stateService.setDirectory(DirectoryType.OneLogin, this.oneLogin);
await this.configurationService.saveSync(this.sync); await this.stateService.setSync(this.sync);
} }
parseKeyFile() { parseKeyFile() {

View File

@@ -5,22 +5,21 @@ import { LogLevelType } from "jslib-common/enums/logLevelType";
import { AuthService } from "./services/auth.service"; import { AuthService } from "./services/auth.service";
import { ConfigurationService } from "./services/configuration.service";
import { I18nService } from "./services/i18n.service"; import { I18nService } from "./services/i18n.service";
import { KeytarSecureStorageService } from "./services/keytarSecureStorage.service"; import { KeytarSecureStorageService } from "./services/keytarSecureStorage.service";
import { LowdbStorageService } from "./services/lowdbStorage.service"; import { LowdbStorageService } from "./services/lowdbStorage.service";
import { NodeApiService } from "./services/nodeApi.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 { SyncService } from "./services/sync.service";
import { CliPlatformUtilsService } from "jslib-node/cli/services/cliPlatformUtils.service"; import { CliPlatformUtilsService } from "jslib-node/cli/services/cliPlatformUtils.service";
import { ConsoleLogService } from "jslib-node/cli/services/consoleLog.service"; import { ConsoleLogService } from "jslib-node/cli/services/consoleLog.service";
import { NodeCryptoFunctionService } from "jslib-node/services/nodeCryptoFunction.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 { AppIdService } from "jslib-common/services/appId.service";
import { CipherService } from "jslib-common/services/cipher.service"; import { CipherService } from "jslib-common/services/cipher.service";
import { CollectionService } from "jslib-common/services/collection.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 { ContainerService } from "jslib-common/services/container.service";
import { CryptoService } from "jslib-common/services/crypto.service"; import { CryptoService } from "jslib-common/services/crypto.service";
import { EnvironmentService } from "jslib-common/services/environment.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 { FolderService } from "jslib-common/services/folder.service";
import { KeyConnectorService } from "jslib-common/services/keyConnector.service"; import { KeyConnectorService } from "jslib-common/services/keyConnector.service";
import { NoopMessagingService } from "jslib-common/services/noopMessaging.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 { PasswordGenerationService } from "jslib-common/services/passwordGeneration.service";
import { PolicyService } from "jslib-common/services/policy.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 { SearchService } from "jslib-common/services/search.service";
import { SendService } from "jslib-common/services/send.service"; import { SendService } from "jslib-common/services/send.service";
import { SettingsService } from "jslib-common/services/settings.service"; import { SettingsService } from "jslib-common/services/settings.service";
import { SyncService as LoginSyncService } from "jslib-common/services/sync.service"; import { SyncService as LoginSyncService } from "jslib-common/services/sync.service";
import { TokenService } from "jslib-common/services/token.service"; import { TokenService } from "jslib-common/services/token.service";
import { UserService } from "jslib-common/services/user.service";
import { StorageService as StorageServiceAbstraction } from "jslib-common/abstractions/storage.service"; import { StorageService as StorageServiceAbstraction } from "jslib-common/abstractions/storage.service";
@@ -54,18 +54,14 @@ export class Main {
secureStorageService: StorageServiceAbstraction; secureStorageService: StorageServiceAbstraction;
i18nService: I18nService; i18nService: I18nService;
platformUtilsService: CliPlatformUtilsService; platformUtilsService: CliPlatformUtilsService;
constantsService: ConstantsService;
cryptoService: CryptoService; cryptoService: CryptoService;
tokenService: TokenService; tokenService: TokenService;
appIdService: AppIdService; appIdService: AppIdService;
apiService: NodeApiService; apiService: NodeApiService;
environmentService: EnvironmentService; environmentService: EnvironmentService;
apiKeyService: ApiKeyService;
userService: UserService;
containerService: ContainerService; containerService: ContainerService;
cryptoFunctionService: NodeCryptoFunctionService; cryptoFunctionService: NodeCryptoFunctionService;
authService: AuthService; authService: AuthService;
configurationService: ConfigurationService;
collectionService: CollectionService; collectionService: CollectionService;
cipherService: CipherService; cipherService: CipherService;
fileUploadService: FileUploadService; fileUploadService: FileUploadService;
@@ -79,6 +75,10 @@ export class Main {
loginSyncService: LoginSyncService; loginSyncService: LoginSyncService;
keyConnectorService: KeyConnectorService; keyConnectorService: KeyConnectorService;
program: Program; program: Program;
stateService: StateService;
stateMigrationService: StateMigrationService;
organizationService: OrganizationService;
providerService: ProviderService;
constructor() { constructor() {
const applicationName = "Bitwarden Directory Connector"; const applicationName = "Bitwarden Directory Connector";
@@ -119,22 +119,36 @@ export class Main {
this.secureStorageService = plaintextSecrets this.secureStorageService = plaintextSecrets
? this.storageService ? this.storageService
: new KeytarSecureStorageService(applicationName); : new KeytarSecureStorageService(applicationName);
this.cryptoService = new CryptoService(
this.stateMigrationService = new StateMigrationService(
this.storageService,
this.secureStorageService
);
this.stateService = new StateService(
this.storageService, this.storageService,
this.secureStorageService, this.secureStorageService,
this.logService,
this.stateMigrationService,
process.env.BITWARDENCLI_CONNECTOR_PLAINTEXT_SECRETS !== "true"
);
this.cryptoService = new CryptoService(
this.cryptoFunctionService, this.cryptoFunctionService,
this.platformUtilsService, this.platformUtilsService,
this.logService this.logService,
this.stateService
); );
this.appIdService = new AppIdService(this.storageService); this.appIdService = new AppIdService(this.storageService);
this.tokenService = new TokenService(this.storageService); this.tokenService = new TokenService(this.stateService);
this.messagingService = new NoopMessagingService(); this.messagingService = new NoopMessagingService();
this.environmentService = new EnvironmentService(this.storageService); this.environmentService = new EnvironmentService(this.stateService);
this.apiService = new NodeApiService( this.apiService = new NodeApiService(
this.tokenService, this.tokenService,
this.platformUtilsService, this.platformUtilsService,
this.environmentService, this.environmentService,
() => refreshToken(this.apiKeyService, this.authService), () => refreshToken(this.stateService, this.authService),
async (expired: boolean) => await this.logout(), async (expired: boolean) => await this.logout(),
"Bitwarden_DC/" + "Bitwarden_DC/" +
this.platformUtilsService.getApplicationVersion() + this.platformUtilsService.getApplicationVersion() +
@@ -143,21 +157,22 @@ export class Main {
")", ")",
(clientId, clientSecret) => this.authService.logInApiKey(clientId, clientSecret) (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.containerService = new ContainerService(this.cryptoService);
this.organizationService = new OrganizationService(this.stateService);
this.keyConnectorService = new KeyConnectorService( this.keyConnectorService = new KeyConnectorService(
this.storageService, this.stateService,
this.userService,
this.cryptoService, this.cryptoService,
this.apiService, this.apiService,
this.tokenService, this.tokenService,
this.logService this.logService,
this.organizationService
); );
this.authService = new AuthService( this.authService = new AuthService(
this.cryptoService, this.cryptoService,
this.apiService, this.apiService,
this.userService,
this.tokenService, this.tokenService,
this.appIdService, this.appIdService,
this.i18nService, this.i18nService,
@@ -165,84 +180,91 @@ export class Main {
this.messagingService, this.messagingService,
null, null,
this.logService, this.logService,
this.apiKeyService,
this.cryptoFunctionService, this.cryptoFunctionService,
this.environmentService, this.environmentService,
this.keyConnectorService this.keyConnectorService,
); this.stateService
this.configurationService = new ConfigurationService(
this.storageService,
this.secureStorageService,
process.env.BITWARDENCLI_CONNECTOR_PLAINTEXT_SECRETS !== "true"
); );
this.syncService = new SyncService( this.syncService = new SyncService(
this.configurationService,
this.logService, this.logService,
this.cryptoFunctionService, this.cryptoFunctionService,
this.apiService, this.apiService,
this.messagingService, this.messagingService,
this.i18nService, this.i18nService,
this.environmentService this.environmentService,
this.stateService
); );
this.policyService = new PolicyService(
this.stateService,
this.organizationService,
this.apiService
);
this.passwordGenerationService = new PasswordGenerationService( this.passwordGenerationService = new PasswordGenerationService(
this.cryptoService, this.cryptoService,
this.storageService, this.policyService,
null 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.fileUploadService = new FileUploadService(this.logService, this.apiService);
this.cipherService = new CipherService( this.cipherService = new CipherService(
this.cryptoService, this.cryptoService,
this.userService,
this.settingsService, this.settingsService,
this.apiService, this.apiService,
this.fileUploadService, this.fileUploadService,
this.storageService,
this.i18nService, this.i18nService,
() => searchService, () => searchService,
this.logService this.logService,
); this.stateService
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.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.loginSyncService = new LoginSyncService(
this.userService,
this.apiService, this.apiService,
this.settingsService, this.settingsService,
this.folderService, this.folderService,
this.cipherService, this.cipherService,
this.cryptoService, this.cryptoService,
this.collectionService, this.collectionService,
this.storageService,
this.messagingService, this.messagingService,
this.policyService, this.policyService,
this.sendService, this.sendService,
this.logService, this.logService,
this.tokenService,
this.keyConnectorService, this.keyConnectorService,
this.stateService,
this.organizationService,
this.providerService,
async (expired: boolean) => this.messagingService.send("logout", { expired: expired }) async (expired: boolean) => this.messagingService.send("logout", { expired: expired })
); );
@@ -256,11 +278,12 @@ export class Main {
async logout() { async logout() {
await this.tokenService.clearToken(); await this.tokenService.clearToken();
await this.apiKeyService.clear(); await this.stateService.clean();
} }
private async init() { private async init() {
await this.storageService.init(); await this.storageService.init();
await this.stateService.init();
this.containerService.attachToWindow(global); this.containerService.attachToWindow(global);
await this.environmentService.setUrlsFromStorage(); await this.environmentService.setUrlsFromStorage();
// Dev Server URLs. Comment out the line above. // Dev Server URLs. Comment out the line above.
@@ -269,16 +292,14 @@ export class Main {
// api: 'http://localhost:4000', // api: 'http://localhost:4000',
// identity: 'http://localhost:33656', // identity: 'http://localhost:33656',
// }); // });
const locale = await this.storageService.get<string>(ConstantsService.localeKey); const locale = await this.stateService.getLocale();
await this.i18nService.init(locale); await this.i18nService.init(locale);
this.authService.init(); this.authService.init();
const installedVersion = await this.storageService.get<string>( const installedVersion = await this.stateService.getInstalledVersion();
ConstantsService.installedVersionKey
);
const currentVersion = await this.platformUtilsService.getApplicationVersion(); const currentVersion = await this.platformUtilsService.getApplicationVersion();
if (installedVersion == null || installedVersion !== currentVersion) { if (installedVersion == null || installedVersion !== currentVersion) {
await this.storageService.save(ConstantsService.installedVersionKey, currentVersion); await this.stateService.setInstalledVersion(currentVersion);
} }
} }
} }

View File

@@ -2,20 +2,16 @@ import * as program from "commander";
import { I18nService } from "jslib-common/abstractions/i18n.service"; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { ConfigurationService } from "../services/configuration.service";
import { Response } from "jslib-node/cli/models/response"; import { Response } from "jslib-node/cli/models/response";
import { MessageResponse } from "jslib-node/cli/models/response/messageResponse"; import { MessageResponse } from "jslib-node/cli/models/response/messageResponse";
import { StateService } from "../abstractions/state.service";
export class ClearCacheCommand { export class ClearCacheCommand {
constructor( constructor(private i18nService: I18nService, private stateService: StateService) {}
private configurationService: ConfigurationService,
private i18nService: I18nService
) {}
async run(cmd: program.OptionValues): Promise<Response> { async run(cmd: program.OptionValues): Promise<Response> {
try { try {
await this.configurationService.clearStatefulSettings(true); await this.stateService.clearSyncSettings(true);
const res = new MessageResponse(this.i18nService.t("syncCacheCleared"), null); const res = new MessageResponse(this.i18nService.t("syncCacheCleared"), null);
return Response.success(res); return Response.success(res);
} catch (e) { } catch (e) {

View File

@@ -3,7 +3,7 @@ import * as program from "commander";
import { EnvironmentService } from "jslib-common/abstractions/environment.service"; import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from "jslib-common/abstractions/i18n.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"; import { DirectoryType } from "../enums/directoryType";
@@ -33,7 +33,7 @@ export class ConfigCommand {
constructor( constructor(
private environmentService: EnvironmentService, private environmentService: EnvironmentService,
private i18nService: I18nService, private i18nService: I18nService,
private configurationService: ConfigurationService private stateService: StateService
) {} ) {}
async run(setting: string, value: string, options: program.OptionValues): Promise<Response> { async run(setting: string, value: string, options: program.OptionValues): Promise<Response> {
@@ -126,35 +126,32 @@ export class ConfigCommand {
} }
private async loadConfig() { private async loadConfig() {
this.directory = await this.configurationService.getDirectoryType(); this.directory = await this.stateService.getDirectoryType();
this.ldap = this.ldap =
(await this.configurationService.getDirectory<LdapConfiguration>(DirectoryType.Ldap)) || (await this.stateService.getDirectory<LdapConfiguration>(DirectoryType.Ldap)) || this.ldap;
this.ldap;
this.gsuite = this.gsuite =
(await this.configurationService.getDirectory<GSuiteConfiguration>(DirectoryType.GSuite)) || (await this.stateService.getDirectory<GSuiteConfiguration>(DirectoryType.GSuite)) ||
this.gsuite; this.gsuite;
this.azure = this.azure =
(await this.configurationService.getDirectory<AzureConfiguration>( (await this.stateService.getDirectory<AzureConfiguration>(
DirectoryType.AzureActiveDirectory DirectoryType.AzureActiveDirectory
)) || this.azure; )) || this.azure;
this.okta = this.okta =
(await this.configurationService.getDirectory<OktaConfiguration>(DirectoryType.Okta)) || (await this.stateService.getDirectory<OktaConfiguration>(DirectoryType.Okta)) || this.okta;
this.okta;
this.oneLogin = this.oneLogin =
(await this.configurationService.getDirectory<OneLoginConfiguration>( (await this.stateService.getDirectory<OneLoginConfiguration>(DirectoryType.OneLogin)) ||
DirectoryType.OneLogin this.oneLogin;
)) || this.oneLogin; this.sync = (await this.stateService.getSync()) || this.sync;
this.sync = (await this.configurationService.getSync()) || this.sync;
} }
private async saveConfig() { private async saveConfig() {
ConnectorUtils.adjustConfigForSave(this.ldap, this.sync); ConnectorUtils.adjustConfigForSave(this.ldap, this.sync);
await this.configurationService.saveDirectoryType(this.directory); await this.stateService.setDirectoryType(this.directory);
await this.configurationService.saveDirectory(DirectoryType.Ldap, this.ldap); await this.stateService.setDirectory(DirectoryType.Ldap, this.ldap);
await this.configurationService.saveDirectory(DirectoryType.GSuite, this.gsuite); await this.stateService.setDirectory(DirectoryType.GSuite, this.gsuite);
await this.configurationService.saveDirectory(DirectoryType.AzureActiveDirectory, this.azure); await this.stateService.setDirectory(DirectoryType.AzureActiveDirectory, this.azure);
await this.configurationService.saveDirectory(DirectoryType.Okta, this.okta); await this.stateService.setDirectory(DirectoryType.Okta, this.okta);
await this.configurationService.saveDirectory(DirectoryType.OneLogin, this.oneLogin); await this.stateService.setDirectory(DirectoryType.OneLogin, this.oneLogin);
await this.configurationService.saveSync(this.sync); await this.stateService.setSync(this.sync);
} }
} }

View File

@@ -1,24 +1,24 @@
import * as program from "commander"; 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 { Response } from "jslib-node/cli/models/response";
import { StringResponse } from "jslib-node/cli/models/response/stringResponse"; import { StringResponse } from "jslib-node/cli/models/response/stringResponse";
export class LastSyncCommand { export class LastSyncCommand {
constructor(private configurationService: ConfigurationService) {} constructor(private stateService: StateService) {}
async run(object: string): Promise<Response> { async run(object: string): Promise<Response> {
try { try {
switch (object.toLowerCase()) { switch (object.toLowerCase()) {
case "groups": case "groups":
const groupsDate = await this.configurationService.getLastGroupSyncDate(); const groupsDate = await this.stateService.getLastGroupSync();
const groupsRes = new StringResponse( const groupsRes = new StringResponse(
groupsDate == null ? null : groupsDate.toISOString() groupsDate == null ? null : groupsDate.toISOString()
); );
return Response.success(groupsRes); return Response.success(groupsRes);
case "users": case "users":
const usersDate = await this.configurationService.getLastUserSyncDate(); const usersDate = await this.stateService.getLastUserSync();
const usersRes = new StringResponse(usersDate == null ? null : usersDate.toISOString()); const usersRes = new StringResponse(usersDate == null ? null : usersDate.toISOString());
return Response.success(usersRes); return Response.success(usersRes);
default: default:

View File

@@ -13,12 +13,15 @@ import { TrayMain } from "jslib-electron/tray.main";
import { UpdaterMain } from "jslib-electron/updater.main"; import { UpdaterMain } from "jslib-electron/updater.main";
import { WindowMain } from "jslib-electron/window.main"; import { WindowMain } from "jslib-electron/window.main";
import { StateService } from "./services/state.service";
export class Main { export class Main {
logService: ElectronLogService; logService: ElectronLogService;
i18nService: I18nService; i18nService: I18nService;
storageService: ElectronStorageService; storageService: ElectronStorageService;
messagingService: ElectronMainMessagingService; messagingService: ElectronMainMessagingService;
keytarStorageListener: KeytarStorageListener; keytarStorageListener: KeytarStorageListener;
stateService: StateService;
windowMain: WindowMain; windowMain: WindowMain;
messagingMain: MessagingMain; messagingMain: MessagingMain;
@@ -51,9 +54,10 @@ export class Main {
this.logService = new ElectronLogService(null, app.getPath("userData")); this.logService = new ElectronLogService(null, app.getPath("userData"));
this.i18nService = new I18nService("en", "./locales/"); this.i18nService = new I18nService("en", "./locales/");
this.storageService = new ElectronStorageService(app.getPath("userData")); this.storageService = new ElectronStorageService(app.getPath("userData"));
this.stateService = new StateService(this.storageService, null, this.logService, null);
this.windowMain = new WindowMain( this.windowMain = new WindowMain(
this.storageService, this.stateService,
this.logService, this.logService,
false, false,
800, 800,
@@ -61,6 +65,7 @@ export class Main {
(arg) => this.processDeepLink(arg), (arg) => this.processDeepLink(arg),
null null
); );
this.menuMain = new MenuMain(this); this.menuMain = new MenuMain(this);
this.updaterMain = new UpdaterMain( this.updaterMain = new UpdaterMain(
this.i18nService, this.i18nService,
@@ -77,7 +82,9 @@ export class Main {
}, },
"bitwardenDirectoryConnector" "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.messagingMain = new MessagingMain(
this.windowMain, this.windowMain,
this.menuMain, this.menuMain,

View File

@@ -0,0 +1,2 @@
// tslint:disable-next-line
export interface IConfiguration {}

47
src/models/account.ts Normal file
View File

@@ -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<Account>) {
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;
}

View File

@@ -1,4 +1,6 @@
export class AzureConfiguration { import { IConfiguration } from "./IConfiguration";
export class AzureConfiguration implements IConfiguration {
identityAuthority: string; identityAuthority: string;
tenant: string; tenant: string;
applicationId: string; applicationId: string;

View File

@@ -1,4 +1,6 @@
export class GSuiteConfiguration { import { IConfiguration } from "./IConfiguration";
export class GSuiteConfiguration implements IConfiguration {
clientEmail: string; clientEmail: string;
privateKey: string; privateKey: string;
domain: string; domain: string;

View File

@@ -1,4 +1,6 @@
export class LdapConfiguration { import { IConfiguration } from "./IConfiguration";
export class LdapConfiguration implements IConfiguration {
ssl = false; ssl = false;
startTls = false; startTls = false;
tlsCaPath: string; tlsCaPath: string;

View File

@@ -1,4 +1,6 @@
export class OktaConfiguration { import { IConfiguration } from "./IConfiguration";
export class OktaConfiguration implements IConfiguration {
orgUrl: string; orgUrl: string;
token: string; token: string;
} }

View File

@@ -1,4 +1,6 @@
export class OneLoginConfiguration { import { IConfiguration } from "./IConfiguration";
export class OneLoginConfiguration implements IConfiguration {
clientId: string; clientId: string;
clientSecret: string; clientSecret: string;
region = "us"; region = "us";

View File

@@ -16,7 +16,6 @@ import { UpdateCommand } from "jslib-node/cli/commands/update.command";
import { BaseProgram } from "jslib-node/cli/baseProgram"; import { BaseProgram } from "jslib-node/cli/baseProgram";
import { ApiKeyService } from "jslib-common/abstractions/apiKey.service";
import { Response } from "jslib-node/cli/models/response"; import { Response } from "jslib-node/cli/models/response";
import { StringResponse } from "jslib-node/cli/models/response/stringResponse"; 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 { export class Program extends BaseProgram {
private apiKeyService: ApiKeyService;
constructor(private main: Main) { constructor(private main: Main) {
super(main.userService, writeLn); super(main.stateService, writeLn);
this.apiKeyService = main.apiKeyService;
} }
async run() { async run() {
@@ -107,7 +103,7 @@ export class Program extends BaseProgram {
this.main.passwordGenerationService, this.main.passwordGenerationService,
this.main.cryptoFunctionService, this.main.cryptoFunctionService,
this.main.platformUtilsService, this.main.platformUtilsService,
this.main.userService, this.main.stateService,
this.main.cryptoService, this.main.cryptoService,
this.main.policyService, this.main.policyService,
"connector", "connector",
@@ -197,7 +193,7 @@ export class Program extends BaseProgram {
}) })
.action(async (object: string) => { .action(async (object: string) => {
await this.exitIfNotAuthed(); await this.exitIfNotAuthed();
const command = new LastSyncCommand(this.main.configurationService); const command = new LastSyncCommand(this.main.stateService);
const response = await command.run(object); const response = await command.run(object);
this.processResponse(response); this.processResponse(response);
}); });
@@ -235,7 +231,7 @@ export class Program extends BaseProgram {
const command = new ConfigCommand( const command = new ConfigCommand(
this.main.environmentService, this.main.environmentService,
this.main.i18nService, this.main.i18nService,
this.main.configurationService this.main.stateService
); );
const response = await command.run(setting, value, options); const response = await command.run(setting, value, options);
this.processResponse(response); this.processResponse(response);
@@ -266,10 +262,7 @@ export class Program extends BaseProgram {
writeLn("", true); writeLn("", true);
}) })
.action(async (options: program.OptionValues) => { .action(async (options: program.OptionValues) => {
const command = new ClearCacheCommand( const command = new ClearCacheCommand(this.main.i18nService, this.main.stateService);
this.main.configurationService,
this.main.i18nService
);
const response = await command.run(options); const response = await command.run(options);
this.processResponse(response); this.processResponse(response);
}); });
@@ -310,10 +303,10 @@ export class Program extends BaseProgram {
} }
async exitIfAuthed() { async exitIfAuthed() {
const authed = await this.apiKeyService.isAuthenticated(); const authed = await this.stateService.getIsAuthenticated();
if (authed) { if (authed) {
const type = await this.apiKeyService.getEntityType(); const type = await this.stateService.getEntityType();
const id = await this.apiKeyService.getEntityId(); const id = await this.stateService.getEntityId();
this.processResponse( this.processResponse(
Response.error("You are already logged in as " + type + "." + id + "."), Response.error("You are already logged in as " + type + "." + id + "."),
true true
@@ -322,7 +315,7 @@ export class Program extends BaseProgram {
} }
async exitIfNotAuthed() { async exitIfNotAuthed() {
const authed = await this.apiKeyService.isAuthenticated(); const authed = await this.stateService.getIsAuthenticated();
if (!authed) { if (!authed) {
this.processResponse(Response.error("You are not logged in."), true); this.processResponse(Response.error("You are not logged in."), true);
} }

View File

@@ -1,15 +1,15 @@
import { ApiKeyService } from "jslib-common/abstractions/apiKey.service";
import { AuthService } from "jslib-common/abstractions/auth.service"; import { AuthService } from "jslib-common/abstractions/auth.service";
import { EnvironmentService } from "jslib-common/abstractions/environment.service"; import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { TokenService } from "jslib-common/abstractions/token.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"; 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 { try {
const clientId = await apiKeyService.getClientId(); const clientId = await stateService.getApiKeyClientId();
const clientSecret = await apiKeyService.getClientSecret(); const clientSecret = await stateService.getApiKeyClientSecret();
if (clientId != null && clientSecret != null) { if (clientId != null && clientSecret != null) {
await authService.logInApiKey(clientId, clientSecret); await authService.logInApiKey(clientId, clientSecret);
} }

View File

@@ -1,5 +1,4 @@
import { ApiService } from "jslib-common/abstractions/api.service"; 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 { AppIdService } from "jslib-common/abstractions/appId.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service"; import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.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 { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { TokenService } from "jslib-common/abstractions/token.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 { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
import { StateService } from "../abstractions/state.service";
import { AuthService as AuthServiceBase } from "jslib-common/services/auth.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 { AuthResult } from "jslib-common/models/domain/authResult";
import { DeviceRequest } from "jslib-common/models/request/deviceRequest"; import { DeviceRequest } from "jslib-common/models/request/deviceRequest";
import { TokenRequest } from "jslib-common/models/request/tokenRequest"; import { TokenRequest } from "jslib-common/models/request/tokenRequest";
import { IdentityTokenResponse } from "jslib-common/models/response/identityTokenResponse"; import { IdentityTokenResponse } from "jslib-common/models/response/identityTokenResponse";
export class AuthService extends AuthServiceBase { export class AuthService extends AuthServiceBase {
constructor( constructor(
cryptoService: CryptoService, cryptoService: CryptoService,
apiService: ApiService, apiService: ApiService,
userService: UserService,
tokenService: TokenService, tokenService: TokenService,
appIdService: AppIdService, appIdService: AppIdService,
i18nService: I18nService, i18nService: I18nService,
@@ -32,15 +35,14 @@ export class AuthService extends AuthServiceBase {
messagingService: MessagingService, messagingService: MessagingService,
vaultTimeoutService: VaultTimeoutService, vaultTimeoutService: VaultTimeoutService,
logService: LogService, logService: LogService,
private apiKeyService: ApiKeyService,
cryptoFunctionService: CryptoFunctionService, cryptoFunctionService: CryptoFunctionService,
environmentService: EnvironmentService, environmentService: EnvironmentService,
keyConnectorService: KeyConnectorService keyConnectorService: KeyConnectorService,
stateService: StateService
) { ) {
super( super(
cryptoService, cryptoService,
apiService, apiService,
userService,
tokenService, tokenService,
appIdService, appIdService,
i18nService, i18nService,
@@ -49,8 +51,9 @@ export class AuthService extends AuthServiceBase {
vaultTimeoutService, vaultTimeoutService,
logService, logService,
cryptoFunctionService, cryptoFunctionService,
environmentService,
keyConnectorService, keyConnectorService,
environmentService,
stateService,
false false
); );
} }
@@ -64,12 +67,13 @@ export class AuthService extends AuthServiceBase {
} }
async logOut(callback: Function) { async logOut(callback: Function) {
this.apiKeyService.clear(); this.stateService.clean();
super.logOut(callback); super.logOut(callback);
} }
private async organizationLogInHelper(clientId: string, clientSecret: string) { private async organizationLogInHelper(clientId: string, clientSecret: string) {
const appId = await this.appIdService.getAppId(); const appId = await this.appIdService.getAppId();
const entityId = clientId.split("organization.")[1];
const deviceRequest = new DeviceRequest(appId, this.platformUtilsService); const deviceRequest = new DeviceRequest(appId, this.platformUtilsService);
const request = new TokenRequest( const request = new TokenRequest(
null, null,
@@ -88,9 +92,33 @@ export class AuthService extends AuthServiceBase {
const tokenResponse = response as IdentityTokenResponse; const tokenResponse = response as IdentityTokenResponse;
result.resetMasterPassword = tokenResponse.resetMasterPassword; result.resetMasterPassword = tokenResponse.resetMasterPassword;
await this.tokenService.setToken(tokenResponse.accessToken); await this.stateService.addAccount(
await this.apiKeyService.setInformation(clientId, clientSecret); 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; return result;
} }
} }

View File

@@ -11,11 +11,11 @@ import { SyncConfiguration } from "../models/syncConfiguration";
import { UserEntry } from "../models/userEntry"; import { UserEntry } from "../models/userEntry";
import { BaseDirectoryService } from "./baseDirectory.service"; import { BaseDirectoryService } from "./baseDirectory.service";
import { ConfigurationService } from "./configuration.service";
import { IDirectoryService } from "./directory.service"; import { IDirectoryService } from "./directory.service";
import { I18nService } from "jslib-common/abstractions/i18n.service"; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service"; import { LogService } from "jslib-common/abstractions/log.service";
import { StateService } from "../abstractions/state.service";
const AzurePublicIdentityAuhtority = "login.microsoftonline.com"; const AzurePublicIdentityAuhtority = "login.microsoftonline.com";
const AzureGovermentIdentityAuhtority = "login.microsoftonline.us"; const AzureGovermentIdentityAuhtority = "login.microsoftonline.us";
@@ -40,28 +40,28 @@ export class AzureDirectoryService extends BaseDirectoryService implements IDire
private accessTokenExpiration: Date; private accessTokenExpiration: Date;
constructor( constructor(
private configurationService: ConfigurationService,
private logService: LogService, private logService: LogService,
private i18nService: I18nService private i18nService: I18nService,
private stateService: StateService
) { ) {
super(); super();
this.init(); this.init();
} }
async getEntries(force: boolean, test: boolean): Promise<[GroupEntry[], UserEntry[]]> { 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) { if (type !== DirectoryType.AzureActiveDirectory) {
return; return;
} }
this.dirConfig = await this.configurationService.getDirectory<AzureConfiguration>( this.dirConfig = await this.stateService.getDirectory<AzureConfiguration>(
DirectoryType.AzureActiveDirectory DirectoryType.AzureActiveDirectory
); );
if (this.dirConfig == null) { if (this.dirConfig == null) {
return; return;
} }
this.syncConfig = await this.configurationService.getSync(); this.syncConfig = await this.stateService.getSync();
if (this.syncConfig == null) { if (this.syncConfig == null) {
return; return;
} }
@@ -130,7 +130,7 @@ export class AzureDirectoryService extends BaseDirectoryService implements IDire
const entries: UserEntry[] = []; const entries: UserEntry[] = [];
let res: any = null; let res: any = null;
const token = await this.configurationService.getUserDeltaToken(); const token = await this.stateService.getUserDelta();
if (!force && token != null) { if (!force && token != null) {
try { try {
const deltaReq = this.client.api(token); const deltaReq = this.client.api(token);
@@ -168,7 +168,7 @@ export class AzureDirectoryService extends BaseDirectoryService implements IDire
if (res[NextLink] == null) { if (res[NextLink] == null) {
if (res[DeltaLink] != null && saveDelta) { if (res[DeltaLink] != null && saveDelta) {
await this.configurationService.saveUserDeltaToken(res[DeltaLink]); await this.stateService.setUserDelta(res[DeltaLink]);
} }
break; break;
} else { } else {

View File

@@ -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<T>(type: DirectoryType): Promise<T> {
const config = await this.storageService.get<T>(Keys.directoryConfigPrefix + type);
if (config == null) {
return config;
}
if (this.useSecureStorageForSecrets) {
switch (type) {
case DirectoryType.Ldap:
(config as any).password = await this.secureStorageService.get<string>(Keys.ldap);
break;
case DirectoryType.AzureActiveDirectory:
(config as any).key = await this.secureStorageService.get<string>(Keys.azure);
break;
case DirectoryType.Okta:
(config as any).token = await this.secureStorageService.get<string>(Keys.okta);
break;
case DirectoryType.GSuite:
(config as any).privateKey = await this.secureStorageService.get<string>(Keys.gsuite);
break;
case DirectoryType.OneLogin:
(config as any).clientSecret = await this.secureStorageService.get<string>(Keys.oneLogin);
break;
}
}
return config;
}
async saveDirectory(
type: DirectoryType,
config:
| LdapConfiguration
| GSuiteConfiguration
| AzureConfiguration
| OktaConfiguration
| OneLoginConfiguration
): Promise<any> {
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<SyncConfiguration> {
return this.storageService.get<SyncConfiguration>(Keys.sync);
}
saveSync(config: SyncConfiguration) {
return this.storageService.save(Keys.sync, config);
}
getDirectoryType(): Promise<DirectoryType> {
return this.storageService.get<DirectoryType>(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<string> {
return this.storageService.get<string>(Keys.userDelta);
}
saveUserDeltaToken(token: string) {
if (token == null) {
return this.storageService.remove(Keys.userDelta);
} else {
return this.storageService.save(Keys.userDelta, token);
}
}
getGroupDeltaToken(): Promise<string> {
return this.storageService.get<string>(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<Date> {
const dateString = await this.storageService.get<string>(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<Date> {
const dateString = await this.storageService.get<string>(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<string> {
return this.storageService.get<string>(Keys.lastSyncHash);
}
saveLastSyncHash(hash: string) {
if (hash == null) {
return this.storageService.remove(Keys.lastSyncHash);
} else {
return this.storageService.save(Keys.lastSyncHash, hash);
}
}
getOrganizationId(): Promise<string> {
return this.storageService.get<string>(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);
}
}
}

View File

@@ -9,11 +9,11 @@ import { SyncConfiguration } from "../models/syncConfiguration";
import { UserEntry } from "../models/userEntry"; import { UserEntry } from "../models/userEntry";
import { BaseDirectoryService } from "./baseDirectory.service"; import { BaseDirectoryService } from "./baseDirectory.service";
import { ConfigurationService } from "./configuration.service";
import { IDirectoryService } from "./directory.service"; import { IDirectoryService } from "./directory.service";
import { I18nService } from "jslib-common/abstractions/i18n.service"; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service"; import { LogService } from "jslib-common/abstractions/log.service";
import { StateService } from "../abstractions/state.service";
export class GSuiteDirectoryService extends BaseDirectoryService implements IDirectoryService { export class GSuiteDirectoryService extends BaseDirectoryService implements IDirectoryService {
private client: JWT; private client: JWT;
@@ -23,28 +23,28 @@ export class GSuiteDirectoryService extends BaseDirectoryService implements IDir
private syncConfig: SyncConfiguration; private syncConfig: SyncConfiguration;
constructor( constructor(
private configurationService: ConfigurationService,
private logService: LogService, private logService: LogService,
private i18nService: I18nService private i18nService: I18nService,
private stateService: StateService
) { ) {
super(); super();
this.service = google.admin("directory_v1"); this.service = google.admin("directory_v1");
} }
async getEntries(force: boolean, test: boolean): Promise<[GroupEntry[], UserEntry[]]> { 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) { if (type !== DirectoryType.GSuite) {
return; return;
} }
this.dirConfig = await this.configurationService.getDirectory<GSuiteConfiguration>( this.dirConfig = await this.stateService.getDirectory<GSuiteConfiguration>(
DirectoryType.GSuite DirectoryType.GSuite
); );
if (this.dirConfig == null) { if (this.dirConfig == null) {
return; return;
} }
this.syncConfig = await this.configurationService.getSync(); this.syncConfig = await this.stateService.getSync();
if (this.syncConfig == null) { if (this.syncConfig == null) {
return; return;
} }

View File

@@ -10,11 +10,11 @@ import { LdapConfiguration } from "../models/ldapConfiguration";
import { SyncConfiguration } from "../models/syncConfiguration"; import { SyncConfiguration } from "../models/syncConfiguration";
import { UserEntry } from "../models/userEntry"; import { UserEntry } from "../models/userEntry";
import { ConfigurationService } from "./configuration.service";
import { IDirectoryService } from "./directory.service"; import { IDirectoryService } from "./directory.service";
import { I18nService } from "jslib-common/abstractions/i18n.service"; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service"; import { LogService } from "jslib-common/abstractions/log.service";
import { StateService } from "../abstractions/state.service";
import { Utils } from "jslib-common/misc/utils"; import { Utils } from "jslib-common/misc/utils";
@@ -26,25 +26,23 @@ export class LdapDirectoryService implements IDirectoryService {
private syncConfig: SyncConfiguration; private syncConfig: SyncConfiguration;
constructor( constructor(
private configurationService: ConfigurationService,
private logService: LogService, private logService: LogService,
private i18nService: I18nService private i18nService: I18nService,
private stateService: StateService
) {} ) {}
async getEntries(force: boolean, test: boolean): Promise<[GroupEntry[], UserEntry[]]> { 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) { if (type !== DirectoryType.Ldap) {
return; return;
} }
this.dirConfig = await this.configurationService.getDirectory<LdapConfiguration>( this.dirConfig = await this.stateService.getDirectory<LdapConfiguration>(DirectoryType.Ldap);
DirectoryType.Ldap
);
if (this.dirConfig == null) { if (this.dirConfig == null) {
return; return;
} }
this.syncConfig = await this.configurationService.getSync(); this.syncConfig = await this.stateService.getSync();
if (this.syncConfig == null) { if (this.syncConfig == null) {
return; return;
} }
@@ -71,7 +69,7 @@ export class LdapDirectoryService implements IDirectoryService {
} }
private async getUsers(force: boolean): Promise<UserEntry[]> { private async getUsers(force: boolean): Promise<UserEntry[]> {
const lastSync = await this.configurationService.getLastUserSyncDate(); const lastSync = await this.stateService.getLastUserSync();
let filter = this.buildBaseFilter(this.syncConfig.userObjectClass, this.syncConfig.userFilter); let filter = this.buildBaseFilter(this.syncConfig.userObjectClass, this.syncConfig.userFilter);
filter = this.buildRevisionFilter(filter, force, lastSync); filter = this.buildRevisionFilter(filter, force, lastSync);
@@ -147,7 +145,7 @@ export class LdapDirectoryService implements IDirectoryService {
private async getGroups(force: boolean): Promise<GroupEntry[]> { private async getGroups(force: boolean): Promise<GroupEntry[]> {
const entries: GroupEntry[] = []; const entries: GroupEntry[] = [];
const lastSync = await this.configurationService.getLastUserSyncDate(); const lastSync = await this.stateService.getLastUserSync();
const originalFilter = this.buildBaseFilter( const originalFilter = this.buildBaseFilter(
this.syncConfig.groupObjectClass, this.syncConfig.groupObjectClass,
this.syncConfig.groupFilter this.syncConfig.groupFilter

View File

@@ -6,13 +6,13 @@ import { SyncConfiguration } from "../models/syncConfiguration";
import { UserEntry } from "../models/userEntry"; import { UserEntry } from "../models/userEntry";
import { BaseDirectoryService } from "./baseDirectory.service"; import { BaseDirectoryService } from "./baseDirectory.service";
import { ConfigurationService } from "./configuration.service";
import { IDirectoryService } from "./directory.service"; import { IDirectoryService } from "./directory.service";
import { I18nService } from "jslib-common/abstractions/i18n.service"; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service"; import { LogService } from "jslib-common/abstractions/log.service";
import * as https from "https"; import * as https from "https";
import { StateService } from "../abstractions/state.service";
const DelayBetweenBuildGroupCallsInMilliseconds = 500; const DelayBetweenBuildGroupCallsInMilliseconds = 500;
@@ -22,27 +22,25 @@ export class OktaDirectoryService extends BaseDirectoryService implements IDirec
private lastBuildGroupCall: number; private lastBuildGroupCall: number;
constructor( constructor(
private configurationService: ConfigurationService,
private logService: LogService, private logService: LogService,
private i18nService: I18nService private i18nService: I18nService,
private stateService: StateService
) { ) {
super(); super();
} }
async getEntries(force: boolean, test: boolean): Promise<[GroupEntry[], UserEntry[]]> { 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) { if (type !== DirectoryType.Okta) {
return; return;
} }
this.dirConfig = await this.configurationService.getDirectory<OktaConfiguration>( this.dirConfig = await this.stateService.getDirectory<OktaConfiguration>(DirectoryType.Okta);
DirectoryType.Okta
);
if (this.dirConfig == null) { if (this.dirConfig == null) {
return; return;
} }
this.syncConfig = await this.configurationService.getSync(); this.syncConfig = await this.stateService.getSync();
if (this.syncConfig == null) { if (this.syncConfig == null) {
return; return;
} }
@@ -68,7 +66,7 @@ export class OktaDirectoryService extends BaseDirectoryService implements IDirec
private async getUsers(force: boolean): Promise<UserEntry[]> { private async getUsers(force: boolean): Promise<UserEntry[]> {
const entries: UserEntry[] = []; 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 oktaFilter = this.buildOktaFilter(this.syncConfig.userFilter, force, lastSync);
const setFilter = this.createCustomSet(this.syncConfig.userFilter); const setFilter = this.createCustomSet(this.syncConfig.userFilter);
@@ -124,7 +122,7 @@ export class OktaDirectoryService extends BaseDirectoryService implements IDirec
setFilter: [boolean, Set<string>] setFilter: [boolean, Set<string>]
): Promise<GroupEntry[]> { ): Promise<GroupEntry[]> {
const entries: GroupEntry[] = []; const entries: GroupEntry[] = [];
const lastSync = await this.configurationService.getLastGroupSyncDate(); const lastSync = await this.stateService.getLastGroupSync();
const oktaFilter = this.buildOktaFilter(this.syncConfig.groupFilter, force, lastSync); const oktaFilter = this.buildOktaFilter(this.syncConfig.groupFilter, force, lastSync);
this.logService.info("Querying groups."); this.logService.info("Querying groups.");

View File

@@ -6,11 +6,11 @@ import { SyncConfiguration } from "../models/syncConfiguration";
import { UserEntry } from "../models/userEntry"; import { UserEntry } from "../models/userEntry";
import { BaseDirectoryService } from "./baseDirectory.service"; import { BaseDirectoryService } from "./baseDirectory.service";
import { ConfigurationService } from "./configuration.service";
import { IDirectoryService } from "./directory.service"; import { IDirectoryService } from "./directory.service";
import { I18nService } from "jslib-common/abstractions/i18n.service"; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service"; import { LogService } from "jslib-common/abstractions/log.service";
import { StateService } from "../abstractions/state.service";
// Basic email validation: something@something.something // Basic email validation: something@something.something
const ValidEmailRegex = /^\S+@\S+\.\S+$/; const ValidEmailRegex = /^\S+@\S+\.\S+$/;
@@ -22,27 +22,27 @@ export class OneLoginDirectoryService extends BaseDirectoryService implements ID
private allUsers: any[] = []; private allUsers: any[] = [];
constructor( constructor(
private configurationService: ConfigurationService,
private logService: LogService, private logService: LogService,
private i18nService: I18nService private i18nService: I18nService,
private stateService: StateService
) { ) {
super(); super();
} }
async getEntries(force: boolean, test: boolean): Promise<[GroupEntry[], UserEntry[]]> { 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) { if (type !== DirectoryType.OneLogin) {
return; return;
} }
this.dirConfig = await this.configurationService.getDirectory<OneLoginConfiguration>( this.dirConfig = await this.stateService.getDirectory<OneLoginConfiguration>(
DirectoryType.OneLogin DirectoryType.OneLogin
); );
if (this.dirConfig == null) { if (this.dirConfig == null) {
return; return;
} }
this.syncConfig = await this.configurationService.getSync(); this.syncConfig = await this.stateService.getSync();
if (this.syncConfig == null) { if (this.syncConfig == null) {
return; return;
} }

View File

@@ -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<Account> 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<T extends IConfiguration>(type: DirectoryType): Promise<T> {
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<any> {
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<string> {
options = this.reconcileOptions(options, await this.defaultSecureStorageOptions());
if (options?.userId == null) {
return null;
}
return await this.secureStorageService.get<string>(
`${options.userId}_${SecureStorageKeys.ldap}`
);
}
async setLdapKey(value: string, options?: StorageOptions): Promise<void> {
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<string> {
options = this.reconcileOptions(options, await this.defaultSecureStorageOptions());
if (options?.userId == null) {
return null;
}
return await this.secureStorageService.get<string>(
`${options.userId}_${SecureStorageKeys.gsuite}`
);
}
async setGsuiteKey(value: string, options?: StorageOptions): Promise<void> {
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<string> {
options = this.reconcileOptions(options, await this.defaultSecureStorageOptions());
if (options?.userId == null) {
return null;
}
return await this.secureStorageService.get<string>(
`${options.userId}_${SecureStorageKeys.azure}`
);
}
async setAzureKey(value: string, options?: StorageOptions): Promise<void> {
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<string> {
options = this.reconcileOptions(options, await this.defaultSecureStorageOptions());
if (options?.userId == null) {
return null;
}
return await this.secureStorageService.get<string>(
`${options.userId}_${SecureStorageKeys.okta}`
);
}
async setOktaKey(value: string, options?: StorageOptions): Promise<void> {
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<string> {
options = this.reconcileOptions(options, await this.defaultSecureStorageOptions());
if (options?.userId == null) {
return null;
}
return await this.secureStorageService.get<string>(
`${options.userId}_${SecureStorageKeys.oneLogin}`
);
}
async setOneLoginKey(value: string, options?: StorageOptions): Promise<void> {
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<string> {
options = this.reconcileOptions(options, await this.defaultSecureStorageOptions());
if (options?.userId == null) {
return null;
}
return await this.secureStorageService.get<string>(
`${options.userId}_${SecureStorageKeys.userDelta}`
);
}
async setUserDelta(value: string, options?: StorageOptions): Promise<void> {
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<string> {
options = this.reconcileOptions(options, await this.defaultSecureStorageOptions());
if (options?.userId == null) {
return null;
}
return await this.secureStorageService.get<string>(
`${options.userId}_${SecureStorageKeys.groupDelta}`
);
}
async setGroupDelta(value: string, options?: StorageOptions): Promise<void> {
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<IConfiguration> {
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<LdapConfiguration> {
return (
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
)?.directoryConfigurations?.ldap;
}
async setLdapConfiguration(value: LdapConfiguration, options?: StorageOptions): Promise<void> {
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<GSuiteConfiguration> {
return (
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
)?.directoryConfigurations?.gsuite;
}
async setGsuiteConfiguration(
value: GSuiteConfiguration,
options?: StorageOptions
): Promise<void> {
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<AzureConfiguration> {
return (
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
)?.directoryConfigurations?.azure;
}
async setAzureConfiguration(value: AzureConfiguration, options?: StorageOptions): Promise<void> {
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<OktaConfiguration> {
return (
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
)?.directoryConfigurations?.okta;
}
async setOktaConfiguration(value: OktaConfiguration, options?: StorageOptions): Promise<void> {
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<OneLoginConfiguration> {
return (
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
)?.directoryConfigurations?.oneLogin;
}
async setOneLoginConfiguration(
value: OneLoginConfiguration,
options?: StorageOptions
): Promise<void> {
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<string> {
return (
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
)?.directorySettings?.organizationId;
}
async setOrganizationId(value: string, options?: StorageOptions): Promise<void> {
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<SyncConfiguration> {
return (
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
)?.directorySettings?.sync;
}
async setSync(value: SyncConfiguration, options?: StorageOptions): Promise<void> {
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<DirectoryType> {
return (
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
)?.directorySettings?.directoryType;
}
async setDirectoryType(value: DirectoryType, options?: StorageOptions): Promise<void> {
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<Date> {
return (
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
)?.directorySettings?.lastUserSync;
}
async setLastUserSync(value: Date, options?: StorageOptions): Promise<void> {
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<Date> {
return (
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
)?.directorySettings?.lastGroupSync;
}
async setLastGroupSync(value: Date, options?: StorageOptions): Promise<void> {
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<string> {
return (
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
)?.directorySettings?.lastSyncHash;
}
async setLastSyncHash(value: string, options?: StorageOptions): Promise<void> {
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<boolean> {
return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)))
?.directorySettings?.syncingDir;
}
async setSyncingDir(value: boolean, options?: StorageOptions): Promise<void> {
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<void> {
await this.scaffoldNewAccountDiskStorage(account);
}
protected async scaffoldNewAccountDiskStorage(account: Account): Promise<void> {
const storedState =
(await this.storageService.get<State<Account>>(
"state",
await this.defaultOnDiskLocalOptions()
)) ?? new State<Account>();
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());
}
}

View File

@@ -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<boolean> {
const currentStateVersion = (
await this.storageService.get<State<Account>>("state", {
htmlStorageLocation: HtmlStorageLocation.Local,
})
)?.globals?.stateVersion;
return currentStateVersion == null || currentStateVersion < this.latestVersion;
}
async migrate(): Promise<void> {
let currentStateVersion =
(await this.storageService.get<State<Account>>("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<string>(ClientKeys.clientIdOld);
const oldClientSecret = await this.storageService.get<string>(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<void> {
await super.migrateStateFrom1To2();
const state = await this.storageService.get<State<Account>>(Keys.state);
const userId = await this.storageService.get<string>(Keys.entityId);
if (userId != null) {
state.accounts[userId] = new Account({
directorySettings: {
directoryType: await this.storageService.get<DirectoryType>(Keys.directoryType),
organizationId: await this.storageService.get<string>(Keys.organizationId),
lastUserSync: await this.storageService.get<Date>(Keys.lastUserSync),
lastGroupSync: await this.storageService.get<Date>(Keys.lastGroupSync),
lastSyncHash: await this.storageService.get<string>(Keys.lastSyncHash),
syncingDir: await this.storageService.get<boolean>(Keys.syncingDir),
sync: await this.storageService.get<SyncConfiguration>(Keys.syncConfig),
},
profile: {
entityId: await this.storageService.get<string>(Keys.entityId),
},
directoryConfigurations: new DirectoryConfigurations(),
clientKeys: {
clientId: await this.storageService.get<string>(ClientKeys.clientId),
clientSecret: await this.storageService.get<string>(ClientKeys.clientSecret),
},
});
}
for (const key in DirectoryType) {
if (await this.storageService.has(SecureStorageKeys.directoryConfigPrefix + key)) {
switch (+key) {
case DirectoryType.Ldap:
state.accounts[userId].directoryConfigurations.ldap =
await this.storageService.get<LdapConfiguration>(
SecureStorageKeys.directoryConfigPrefix + key
);
break;
case DirectoryType.GSuite:
state.accounts[userId].directoryConfigurations.gsuite =
await this.storageService.get<GSuiteConfiguration>(
SecureStorageKeys.directoryConfigPrefix + key
);
break;
case DirectoryType.AzureActiveDirectory:
state.accounts[userId].directoryConfigurations.azure =
await this.storageService.get<AzureConfiguration>(
SecureStorageKeys.directoryConfigPrefix + key
);
break;
case DirectoryType.Okta:
state.accounts[userId].directoryConfigurations.okta =
await this.storageService.get<OktaConfiguration>(
SecureStorageKeys.directoryConfigPrefix + key
);
break;
case DirectoryType.OneLogin:
state.accounts[userId].directoryConfigurations.oneLogin =
await this.storageService.get<OneLoginConfiguration>(
SecureStorageKeys.directoryConfigPrefix + key
);
break;
}
await this.storageService.remove(SecureStorageKeys.directoryConfigPrefix + key);
}
}
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]);
}
}
}
}
}

View File

@@ -15,8 +15,8 @@ import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { Utils } from "jslib-common/misc/utils"; import { Utils } from "jslib-common/misc/utils";
import { StateService } from "../abstractions/state.service";
import { AzureDirectoryService } from "./azure-directory.service"; import { AzureDirectoryService } from "./azure-directory.service";
import { ConfigurationService } from "./configuration.service";
import { IDirectoryService } from "./directory.service"; import { IDirectoryService } from "./directory.service";
import { GSuiteDirectoryService } from "./gsuite-directory.service"; import { GSuiteDirectoryService } from "./gsuite-directory.service";
import { LdapDirectoryService } from "./ldap-directory.service"; import { LdapDirectoryService } from "./ldap-directory.service";
@@ -27,17 +27,17 @@ export class SyncService {
private dirType: DirectoryType; private dirType: DirectoryType;
constructor( constructor(
private configurationService: ConfigurationService,
private logService: LogService, private logService: LogService,
private cryptoFunctionService: CryptoFunctionService, private cryptoFunctionService: CryptoFunctionService,
private apiService: ApiService, private apiService: ApiService,
private messagingService: MessagingService, private messagingService: MessagingService,
private i18nService: I18nService, private i18nService: I18nService,
private environmentService: EnvironmentService private environmentService: EnvironmentService,
private stateService: StateService
) {} ) {}
async sync(force: boolean, test: boolean): Promise<[GroupEntry[], UserEntry[]]> { 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) { if (this.dirType == null) {
throw new Error("No directory configured."); throw new Error("No directory configured.");
} }
@@ -47,9 +47,9 @@ export class SyncService {
throw new Error("Cannot load directory service."); throw new Error("Cannot load directory service.");
} }
const syncConfig = await this.configurationService.getSync(); const syncConfig = await this.stateService.getSync();
const startingGroupDelta = await this.configurationService.getGroupDeltaToken(); const startingGroupDelta = await this.stateService.getGroupDelta();
const startingUserDelta = await this.configurationService.getUserDeltaToken(); const startingUserDelta = await this.stateService.getUserDelta();
const now = new Date(); const now = new Date();
this.messagingService.send("dirSyncStarted"); this.messagingService.send("dirSyncStarted");
@@ -90,7 +90,7 @@ export class SyncService {
); );
const reqJson = JSON.stringify(req); const reqJson = JSON.stringify(req);
const orgId = await this.configurationService.getOrganizationId(); const orgId = await this.stateService.getOrganizationId();
if (orgId == null) { if (orgId == null) {
throw new Error("Organization not set."); throw new Error("Organization not set.");
} }
@@ -112,11 +112,11 @@ export class SyncService {
if (hashBuff != null) { if (hashBuff != null) {
hash = Utils.fromBufferToB64(hashBuff); hash = Utils.fromBufferToB64(hashBuff);
} }
const lastHash = await this.configurationService.getLastSyncHash(); const lastHash = await this.stateService.getLastSyncHash();
if (lastHash == null || (hash !== lastHash && hashLegacy !== lastHash)) { if (lastHash == null || (hash !== lastHash && hashLegacy !== lastHash)) {
await this.apiService.postPublicImportDirectory(req); await this.apiService.postPublicImportDirectory(req);
await this.configurationService.saveLastSyncHash(hash); await this.stateService.setLastSyncHash(hash);
} else { } else {
groups = null; groups = null;
users = null; users = null;
@@ -127,8 +127,8 @@ export class SyncService {
return [groups, users]; return [groups, users];
} catch (e) { } catch (e) {
if (!test) { if (!test) {
await this.configurationService.saveGroupDeltaToken(startingGroupDelta); await this.stateService.setGroupDelta(startingGroupDelta);
await this.configurationService.saveUserDeltaToken(startingUserDelta); await this.stateService.setUserDelta(startingUserDelta);
} }
this.messagingService.send("dirSyncCompleted", { successfully: false }); this.messagingService.send("dirSyncCompleted", { successfully: false });
@@ -200,35 +200,15 @@ export class SyncService {
private getDirectoryService(): IDirectoryService { private getDirectoryService(): IDirectoryService {
switch (this.dirType) { switch (this.dirType) {
case DirectoryType.GSuite: case DirectoryType.GSuite:
return new GSuiteDirectoryService( return new GSuiteDirectoryService(this.logService, this.i18nService, this.stateService);
this.configurationService,
this.logService,
this.i18nService
);
case DirectoryType.AzureActiveDirectory: case DirectoryType.AzureActiveDirectory:
return new AzureDirectoryService( return new AzureDirectoryService(this.logService, this.i18nService, this.stateService);
this.configurationService,
this.logService,
this.i18nService
);
case DirectoryType.Ldap: case DirectoryType.Ldap:
return new LdapDirectoryService( return new LdapDirectoryService(this.logService, this.i18nService, this.stateService);
this.configurationService,
this.logService,
this.i18nService
);
case DirectoryType.Okta: case DirectoryType.Okta:
return new OktaDirectoryService( return new OktaDirectoryService(this.logService, this.i18nService, this.stateService);
this.configurationService,
this.logService,
this.i18nService
);
case DirectoryType.OneLogin: case DirectoryType.OneLogin:
return new OneLoginDirectoryService( return new OneLoginDirectoryService(this.logService, this.i18nService, this.stateService);
this.configurationService,
this.logService,
this.i18nService
);
default: default:
return null; return null;
} }
@@ -263,10 +243,10 @@ export class SyncService {
private async saveSyncTimes(syncConfig: SyncConfiguration, time: Date) { private async saveSyncTimes(syncConfig: SyncConfiguration, time: Date) {
if (syncConfig.groups) { if (syncConfig.groups) {
await this.configurationService.saveLastGroupSyncDate(time); await this.stateService.setLastGroupSync(time);
} }
if (syncConfig.users) { if (syncConfig.users) {
await this.configurationService.saveLastUserSyncDate(time); await this.stateService.setLastUserSync(time);
} }
} }
} }