1
0
mirror of https://github.com/bitwarden/directory-connector synced 2025-12-05 23:53:21 +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
run: |
sudo apt install libsecret-1-0
sudo apt install libsecret-1-0 dbus-x11 gnome-keyring
eval $(dbus-launch --sh-syntax)
eval $(echo -n "" | /usr/bin/gnome-keyring-daemon --login)
eval $(/usr/bin/gnome-keyring-daemon --components=secrets --start)
mkdir -p test/linux
unzip ./dist-cli/bwdc-linux-$_PACKAGE_VERSION.zip -d ./test/linux

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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;
tenant: string;
applicationId: string;

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,15 +1,15 @@
import { ApiKeyService } from "jslib-common/abstractions/apiKey.service";
import { AuthService } from "jslib-common/abstractions/auth.service";
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { TokenService } from "jslib-common/abstractions/token.service";
import { StateService } from "../abstractions/state.service";
import { ApiService as ApiServiceBase } from "jslib-common/services/api.service";
export async function refreshToken(apiKeyService: ApiKeyService, authService: AuthService) {
export async function refreshToken(stateService: StateService, authService: AuthService) {
try {
const clientId = await apiKeyService.getClientId();
const clientSecret = await apiKeyService.getClientSecret();
const clientId = await stateService.getApiKeyClientId();
const clientSecret = await stateService.getApiKeyClientSecret();
if (clientId != null && clientSecret != null) {
await authService.logInApiKey(clientId, clientSecret);
}

View File

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

View File

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

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

View File

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

View File

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

View File

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