mirror of
https://github.com/bitwarden/directory-connector
synced 2026-02-05 19:23:34 +00:00
Compare commits
4 Commits
ac/pm-3100
...
state-serv
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a0e74948bd | ||
|
|
9f8018e8f8 | ||
|
|
0bff38c459 | ||
|
|
94ff20f69f |
@@ -3,5 +3,6 @@ export enum StateVersion {
|
||||
Two = 2, // Move to a typed State object
|
||||
Three = 3, // Fix migration of users' premium status
|
||||
Four = 4, // Fix 'Never Lock' option by removing stale data
|
||||
Latest = Four,
|
||||
Five = 5, // New state service implementation
|
||||
Latest = Five,
|
||||
}
|
||||
|
||||
54
src/abstractions/state-vNext.service.ts
Normal file
54
src/abstractions/state-vNext.service.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { StorageOptions } from "@/jslib/common/src/models/domain/storageOptions";
|
||||
|
||||
import { DirectoryType } from "@/src/enums/directoryType";
|
||||
import { EntraIdConfiguration } from "@/src/models/entraIdConfiguration";
|
||||
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 StateServiceVNext {
|
||||
getDirectory: <IConfiguration>(type: DirectoryType) => Promise<IConfiguration>;
|
||||
setDirectory: (
|
||||
type: DirectoryType,
|
||||
config:
|
||||
| LdapConfiguration
|
||||
| GSuiteConfiguration
|
||||
| EntraIdConfiguration
|
||||
| OktaConfiguration
|
||||
| OneLoginConfiguration,
|
||||
) => Promise<any>;
|
||||
getLdapConfiguration: (options?: StorageOptions) => Promise<LdapConfiguration>;
|
||||
setLdapConfiguration: (value: LdapConfiguration, options?: StorageOptions) => Promise<void>;
|
||||
getGsuiteConfiguration: (options?: StorageOptions) => Promise<GSuiteConfiguration>;
|
||||
setGsuiteConfiguration: (value: GSuiteConfiguration, options?: StorageOptions) => Promise<void>;
|
||||
getEntraConfiguration: (options?: StorageOptions) => Promise<EntraIdConfiguration>;
|
||||
setEntraConfiguration: (value: EntraIdConfiguration, 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>;
|
||||
}
|
||||
@@ -31,12 +31,14 @@ import { DefaultDirectoryFactoryService } from "@/src/services/directory-factory
|
||||
import { SingleRequestBuilder } from "@/src/services/single-request-builder";
|
||||
|
||||
import { AuthService as AuthServiceAbstraction } from "../../abstractions/auth.service";
|
||||
import { StateServiceVNext } from "../../abstractions/state-vNext.service";
|
||||
import { StateService as StateServiceAbstraction } from "../../abstractions/state.service";
|
||||
import { Account } from "../../models/account";
|
||||
import { AuthService } from "../../services/auth.service";
|
||||
import { I18nService } from "../../services/i18n.service";
|
||||
import { StateService } from "../../services/state.service";
|
||||
import { StateMigrationService } from "../../services/stateMigration.service";
|
||||
import { StateServiceVNextImplementation } from "../../services/state-service/state-vNext.service";
|
||||
import { StateService } from "../../services/state-service/state.service";
|
||||
import { StateMigrationService } from "../../services/state-service/stateMigration.service";
|
||||
import { SyncService } from "../../services/sync.service";
|
||||
|
||||
import { AuthGuardService } from "./auth-guard.service";
|
||||
@@ -222,6 +224,29 @@ export function initFactory(
|
||||
StateMigrationServiceAbstraction,
|
||||
],
|
||||
}),
|
||||
// Use new StateServiceVNext with flat key-value structure (new interface)
|
||||
safeProvider({
|
||||
provide: StateServiceVNext,
|
||||
useFactory: (
|
||||
storageService: StorageServiceAbstraction,
|
||||
secureStorageService: StorageServiceAbstraction,
|
||||
logService: LogServiceAbstraction,
|
||||
stateMigrationService: StateMigrationServiceAbstraction,
|
||||
) =>
|
||||
new StateServiceVNextImplementation(
|
||||
storageService,
|
||||
secureStorageService,
|
||||
logService,
|
||||
stateMigrationService,
|
||||
true,
|
||||
),
|
||||
deps: [
|
||||
StorageServiceAbstraction,
|
||||
SECURE_STORAGE,
|
||||
LogServiceAbstraction,
|
||||
StateMigrationServiceAbstraction,
|
||||
],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: SingleRequestBuilder,
|
||||
deps: [],
|
||||
@@ -233,7 +258,12 @@ export function initFactory(
|
||||
safeProvider({
|
||||
provide: DirectoryFactoryService,
|
||||
useClass: DefaultDirectoryFactoryService,
|
||||
deps: [LogServiceAbstraction, I18nServiceAbstraction, StateServiceAbstraction],
|
||||
deps: [
|
||||
LogServiceAbstraction,
|
||||
I18nServiceAbstraction,
|
||||
StateServiceAbstraction,
|
||||
StateServiceVNext,
|
||||
],
|
||||
}),
|
||||
] satisfies SafeProvider[],
|
||||
})
|
||||
|
||||
16
src/bwdc.ts
16
src/bwdc.ts
@@ -18,6 +18,7 @@ import { NodeApiService } from "@/jslib/node/src/services/nodeApi.service";
|
||||
import { NodeCryptoFunctionService } from "@/jslib/node/src/services/nodeCryptoFunction.service";
|
||||
|
||||
import { DirectoryFactoryService } from "./abstractions/directory-factory.service";
|
||||
import { StateServiceVNext } from "./abstractions/state-vNext.service";
|
||||
import { Account } from "./models/account";
|
||||
import { Program } from "./program";
|
||||
import { AuthService } from "./services/auth.service";
|
||||
@@ -27,8 +28,9 @@ import { I18nService } from "./services/i18n.service";
|
||||
import { KeytarSecureStorageService } from "./services/keytarSecureStorage.service";
|
||||
import { LowdbStorageService } from "./services/lowdbStorage.service";
|
||||
import { SingleRequestBuilder } from "./services/single-request-builder";
|
||||
import { StateService } from "./services/state.service";
|
||||
import { StateMigrationService } from "./services/stateMigration.service";
|
||||
import { StateServiceVNextImplementation } from "./services/state-service/state-vNext.service";
|
||||
import { StateService } from "./services/state-service/state.service";
|
||||
import { StateMigrationService } from "./services/state-service/stateMigration.service";
|
||||
import { SyncService } from "./services/sync.service";
|
||||
|
||||
// eslint-disable-next-line
|
||||
@@ -53,6 +55,7 @@ export class Main {
|
||||
cryptoFunctionService: NodeCryptoFunctionService;
|
||||
authService: AuthService;
|
||||
syncService: SyncService;
|
||||
stateServiceVNext: StateServiceVNext;
|
||||
stateService: StateService;
|
||||
stateMigrationService: StateMigrationService;
|
||||
directoryFactoryService: DirectoryFactoryService;
|
||||
@@ -116,6 +119,14 @@ export class Main {
|
||||
process.env.BITWARDENCLI_CONNECTOR_PLAINTEXT_SECRETS !== "true",
|
||||
new StateFactory(GlobalState, Account),
|
||||
);
|
||||
// Use new StateServiceVNext with flat key-value structure
|
||||
this.stateServiceVNext = new StateServiceVNextImplementation(
|
||||
this.storageService,
|
||||
this.secureStorageService,
|
||||
this.logService,
|
||||
this.stateMigrationService,
|
||||
process.env.BITWARDENCLI_CONNECTOR_PLAINTEXT_SECRETS !== "true",
|
||||
);
|
||||
|
||||
this.cryptoService = new CryptoService(
|
||||
this.cryptoFunctionService,
|
||||
@@ -157,6 +168,7 @@ export class Main {
|
||||
this.logService,
|
||||
this.i18nService,
|
||||
this.stateService,
|
||||
this.stateServiceVNext,
|
||||
);
|
||||
|
||||
this.batchRequestBuilder = new BatchRequestBuilder();
|
||||
|
||||
13
src/main.ts
13
src/main.ts
@@ -11,12 +11,14 @@ import { TrayMain } from "@/jslib/electron/src/tray.main";
|
||||
import { UpdaterMain } from "@/jslib/electron/src/updater.main";
|
||||
import { WindowMain } from "@/jslib/electron/src/window.main";
|
||||
|
||||
import { StateServiceVNext } from "./abstractions/state-vNext.service";
|
||||
import { DCCredentialStorageListener } from "./main/credential-storage-listener";
|
||||
import { MenuMain } from "./main/menu.main";
|
||||
import { MessagingMain } from "./main/messaging.main";
|
||||
import { Account } from "./models/account";
|
||||
import { I18nService } from "./services/i18n.service";
|
||||
import { StateService } from "./services/state.service";
|
||||
import { StateServiceVNextImplementation } from "./services/state-service/state-vNext.service";
|
||||
import { StateService } from "./services/state-service/state.service";
|
||||
|
||||
export class Main {
|
||||
logService: ElectronLogService;
|
||||
@@ -24,6 +26,7 @@ export class Main {
|
||||
storageService: ElectronStorageService;
|
||||
messagingService: ElectronMainMessagingService;
|
||||
credentialStorageListener: DCCredentialStorageListener;
|
||||
stateServiceVNext: StateServiceVNext;
|
||||
stateService: StateService;
|
||||
|
||||
windowMain: WindowMain;
|
||||
@@ -66,6 +69,14 @@ export class Main {
|
||||
true,
|
||||
new StateFactory(GlobalState, Account),
|
||||
);
|
||||
// Use new StateServiceVNext with flat key-value structure
|
||||
this.stateServiceVNext = new StateServiceVNextImplementation(
|
||||
this.storageService,
|
||||
null,
|
||||
this.logService,
|
||||
null,
|
||||
true,
|
||||
);
|
||||
|
||||
this.windowMain = new WindowMain(
|
||||
this.stateService,
|
||||
|
||||
108
src/models/state.model.ts
Normal file
108
src/models/state.model.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
// ===================================================================
|
||||
// vNext Storage Keys (Flat key-value structure)
|
||||
// ===================================================================
|
||||
|
||||
export const StorageKeysVNext = {
|
||||
stateVersion: "stateVersion",
|
||||
directoryType: "directoryType",
|
||||
organizationId: "organizationId",
|
||||
directory_ldap: "directory_ldap",
|
||||
directory_gsuite: "directory_gsuite",
|
||||
directory_entra: "directory_entra",
|
||||
directory_okta: "directory_okta",
|
||||
directory_onelogin: "directory_onelogin",
|
||||
sync: "sync",
|
||||
syncingDir: "syncingDir",
|
||||
};
|
||||
|
||||
export const SecureStorageKeysVNext: { [key: string]: any } = {
|
||||
ldap: "secret_ldap",
|
||||
gsuite: "secret_gsuite",
|
||||
// Azure Active Directory was renamed to Entra ID, but we've kept the old property name
|
||||
// to be backwards compatible with existing configurations.
|
||||
azure: "secret_azure",
|
||||
entra: "secret_entra",
|
||||
okta: "secret_okta",
|
||||
oneLogin: "secret_oneLogin",
|
||||
userDelta: "userDeltaToken",
|
||||
groupDelta: "groupDeltaToken",
|
||||
lastUserSync: "lastUserSync",
|
||||
lastGroupSync: "lastGroupSync",
|
||||
lastSyncHash: "lastSyncHash",
|
||||
};
|
||||
|
||||
// ===================================================================
|
||||
// Legacy Storage Keys (Account-based hierarchy)
|
||||
// ===================================================================
|
||||
|
||||
export const SecureStorageKeysLegacy = {
|
||||
ldap: "ldapPassword",
|
||||
gsuite: "gsuitePrivateKey",
|
||||
// Azure Active Directory was renamed to Entra ID, but we've kept the old property name
|
||||
// to be backwards compatible with existing configurations.
|
||||
azure: "azureKey",
|
||||
entra: "entraKey",
|
||||
okta: "oktaToken",
|
||||
oneLogin: "oneLoginClientSecret",
|
||||
userDelta: "userDeltaToken",
|
||||
groupDelta: "groupDeltaToken",
|
||||
lastUserSync: "lastUserSync",
|
||||
lastGroupSync: "lastGroupSync",
|
||||
lastSyncHash: "lastSyncHash",
|
||||
};
|
||||
|
||||
export const TempKeys = {
|
||||
tempAccountSettings: "tempAccountSettings",
|
||||
tempDirectoryConfigs: "tempDirectoryConfigs",
|
||||
tempDirectorySettings: "tempDirectorySettings",
|
||||
};
|
||||
|
||||
// ===================================================================
|
||||
// Migration Storage Keys
|
||||
// ===================================================================
|
||||
|
||||
export const SecureStorageKeysMigration: { [key: string]: any } = {
|
||||
ldap: "ldapPassword",
|
||||
gsuite: "gsuitePrivateKey",
|
||||
azure: "azureKey",
|
||||
entra: "entraIdKey",
|
||||
okta: "oktaToken",
|
||||
oneLogin: "oneLoginClientSecret",
|
||||
directoryConfigPrefix: "directoryConfig_",
|
||||
sync: "syncConfig",
|
||||
directoryType: "directoryType",
|
||||
organizationId: "organizationId",
|
||||
};
|
||||
|
||||
export const MigrationKeys: { [key: string]: any } = {
|
||||
entityId: "entityId",
|
||||
directoryType: "directoryType",
|
||||
organizationId: "organizationId",
|
||||
lastUserSync: "lastUserSync",
|
||||
lastGroupSync: "lastGroupSync",
|
||||
lastSyncHash: "lastSyncHash",
|
||||
syncingDir: "syncingDir",
|
||||
syncConfig: "syncConfig",
|
||||
userDelta: "userDeltaToken",
|
||||
groupDelta: "groupDeltaToken",
|
||||
tempDirectoryConfigs: "tempDirectoryConfigs",
|
||||
tempDirectorySettings: "tempDirectorySettings",
|
||||
};
|
||||
|
||||
export const MigrationStateKeys = {
|
||||
global: "global",
|
||||
authenticatedAccounts: "authenticatedAccounts",
|
||||
};
|
||||
|
||||
export const MigrationClientKeys: { [key: string]: any } = {
|
||||
clientIdOld: "clientId",
|
||||
clientId: "apikey_clientId",
|
||||
clientSecretOld: "clientSecret",
|
||||
clientSecret: "apikey_clientSecret",
|
||||
};
|
||||
|
||||
// ===================================================================
|
||||
// Shared Constants
|
||||
// ===================================================================
|
||||
|
||||
export const StoredSecurely = "[STORED SECURELY]";
|
||||
@@ -15,7 +15,7 @@ import { MessagingService } from "../../jslib/common/src/abstractions/messaging.
|
||||
import { Account, DirectoryConfigurations, DirectorySettings } from "../models/account";
|
||||
|
||||
import { AuthService } from "./auth.service";
|
||||
import { StateService } from "./state.service";
|
||||
import { StateService } from "./state-service/state.service";
|
||||
|
||||
const clientId = "organization.CLIENT_ID";
|
||||
const clientSecret = "CLIENT_SECRET";
|
||||
|
||||
@@ -2,6 +2,7 @@ import { I18nService } from "@/jslib/common/src/abstractions/i18n.service";
|
||||
import { LogService } from "@/jslib/common/src/abstractions/log.service";
|
||||
|
||||
import { DirectoryFactoryService } from "../abstractions/directory-factory.service";
|
||||
import { StateServiceVNext } from "../abstractions/state-vNext.service";
|
||||
import { StateService } from "../abstractions/state.service";
|
||||
import { DirectoryType } from "../enums/directoryType";
|
||||
|
||||
@@ -16,12 +17,18 @@ export class DefaultDirectoryFactoryService implements DirectoryFactoryService {
|
||||
private logService: LogService,
|
||||
private i18nService: I18nService,
|
||||
private stateService: StateService,
|
||||
private stateServiceVNext: StateServiceVNext,
|
||||
) {}
|
||||
|
||||
createService(directoryType: DirectoryType) {
|
||||
switch (directoryType) {
|
||||
case DirectoryType.GSuite:
|
||||
return new GSuiteDirectoryService(this.logService, this.i18nService, this.stateService);
|
||||
return new GSuiteDirectoryService(
|
||||
this.logService,
|
||||
this.i18nService,
|
||||
this.stateService,
|
||||
this.stateServiceVNext,
|
||||
);
|
||||
case DirectoryType.EntraID:
|
||||
return new EntraIdDirectoryService(this.logService, this.i18nService, this.stateService);
|
||||
case DirectoryType.Ldap:
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { config as dotenvConfig } from "dotenv";
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
|
||||
import { StateServiceVNext } from "@/src/abstractions/state-vNext.service";
|
||||
|
||||
import { I18nService } from "../../../jslib/common/src/abstractions/i18n.service";
|
||||
import { LogService } from "../../../jslib/common/src/abstractions/log.service";
|
||||
import {
|
||||
@@ -10,7 +12,7 @@ import {
|
||||
import { groupFixtures } from "../../../utils/google-workspace/group-fixtures";
|
||||
import { userFixtures } from "../../../utils/google-workspace/user-fixtures";
|
||||
import { DirectoryType } from "../../enums/directoryType";
|
||||
import { StateService } from "../state.service";
|
||||
import { StateService } from "../state-service/state.service";
|
||||
|
||||
import { GSuiteDirectoryService } from "./gsuite-directory.service";
|
||||
|
||||
@@ -35,6 +37,7 @@ describe("gsuiteDirectoryService", () => {
|
||||
let logService: MockProxy<LogService>;
|
||||
let i18nService: MockProxy<I18nService>;
|
||||
let stateService: MockProxy<StateService>;
|
||||
let stateServiceVNext: MockProxy<StateServiceVNext>;
|
||||
|
||||
let directoryService: GSuiteDirectoryService;
|
||||
|
||||
@@ -42,23 +45,31 @@ describe("gsuiteDirectoryService", () => {
|
||||
logService = mock();
|
||||
i18nService = mock();
|
||||
stateService = mock();
|
||||
stateServiceVNext = mock();
|
||||
|
||||
stateService.getDirectoryType.mockResolvedValue(DirectoryType.GSuite);
|
||||
stateServiceVNext.getDirectoryType.mockResolvedValue(DirectoryType.GSuite);
|
||||
stateService.getLastUserSync.mockResolvedValue(null); // do not filter results by last modified date
|
||||
i18nService.t.mockImplementation((id) => id); // passthrough implementation for any error messages
|
||||
|
||||
directoryService = new GSuiteDirectoryService(logService, i18nService, stateService);
|
||||
directoryService = new GSuiteDirectoryService(
|
||||
logService,
|
||||
i18nService,
|
||||
stateService,
|
||||
stateServiceVNext,
|
||||
);
|
||||
});
|
||||
|
||||
it("syncs without using filters (includes test data)", async () => {
|
||||
const directoryConfig = getGSuiteConfiguration();
|
||||
stateService.getDirectory.calledWith(DirectoryType.GSuite).mockResolvedValue(directoryConfig);
|
||||
stateServiceVNext.getDirectory
|
||||
.calledWith(DirectoryType.GSuite)
|
||||
.mockResolvedValue(directoryConfig);
|
||||
|
||||
const syncConfig = getSyncConfiguration({
|
||||
groups: true,
|
||||
users: true,
|
||||
});
|
||||
stateService.getSync.mockResolvedValue(syncConfig);
|
||||
stateServiceVNext.getSync.mockResolvedValue(syncConfig);
|
||||
|
||||
const result = await directoryService.getEntries(true, true);
|
||||
|
||||
@@ -68,7 +79,9 @@ describe("gsuiteDirectoryService", () => {
|
||||
|
||||
it("syncs using user and group filters (exact match for test data)", async () => {
|
||||
const directoryConfig = getGSuiteConfiguration();
|
||||
stateService.getDirectory.calledWith(DirectoryType.GSuite).mockResolvedValue(directoryConfig);
|
||||
stateServiceVNext.getDirectory
|
||||
.calledWith(DirectoryType.GSuite)
|
||||
.mockResolvedValue(directoryConfig);
|
||||
|
||||
const syncConfig = getSyncConfiguration({
|
||||
groups: true,
|
||||
@@ -76,7 +89,7 @@ describe("gsuiteDirectoryService", () => {
|
||||
userFilter: INTEGRATION_USER_FILTER,
|
||||
groupFilter: INTEGRATION_GROUP_FILTER,
|
||||
});
|
||||
stateService.getSync.mockResolvedValue(syncConfig);
|
||||
stateServiceVNext.getSync.mockResolvedValue(syncConfig);
|
||||
|
||||
const result = await directoryService.getEntries(true, true);
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ import { admin_directory_v1, google } from "googleapis";
|
||||
import { I18nService } from "@/jslib/common/src/abstractions/i18n.service";
|
||||
import { LogService } from "@/jslib/common/src/abstractions/log.service";
|
||||
|
||||
import { StateServiceVNext } from "@/src/abstractions/state-vNext.service";
|
||||
|
||||
import { StateService } from "../../abstractions/state.service";
|
||||
import { DirectoryType } from "../../enums/directoryType";
|
||||
import { GroupEntry } from "../../models/groupEntry";
|
||||
@@ -25,25 +27,26 @@ export class GSuiteDirectoryService extends BaseDirectoryService implements IDir
|
||||
private logService: LogService,
|
||||
private i18nService: I18nService,
|
||||
private stateService: StateService,
|
||||
private stateServiceVNext: StateServiceVNext,
|
||||
) {
|
||||
super();
|
||||
this.service = google.admin("directory_v1");
|
||||
}
|
||||
|
||||
async getEntries(force: boolean, test: boolean): Promise<[GroupEntry[], UserEntry[]]> {
|
||||
const type = await this.stateService.getDirectoryType();
|
||||
const type = await this.stateServiceVNext.getDirectoryType();
|
||||
if (type !== DirectoryType.GSuite) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.dirConfig = await this.stateService.getDirectory<GSuiteConfiguration>(
|
||||
this.dirConfig = await this.stateServiceVNext.getDirectory<GSuiteConfiguration>(
|
||||
DirectoryType.GSuite,
|
||||
);
|
||||
if (this.dirConfig == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.syncConfig = await this.stateService.getSync();
|
||||
this.syncConfig = await this.stateServiceVNext.getSync();
|
||||
if (this.syncConfig == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
import { groupFixtures } from "../../../utils/openldap/group-fixtures";
|
||||
import { userFixtures } from "../../../utils/openldap/user-fixtures";
|
||||
import { DirectoryType } from "../../enums/directoryType";
|
||||
import { StateService } from "../state.service";
|
||||
import { StateService } from "../state-service/state.service";
|
||||
|
||||
import { LdapDirectoryService } from "./ldap-directory.service";
|
||||
|
||||
|
||||
488
src/services/state-service/state-vNext.service.spec.ts
Normal file
488
src/services/state-service/state-vNext.service.spec.ts
Normal file
@@ -0,0 +1,488 @@
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
|
||||
import { LogService } from "@/jslib/common/src/abstractions/log.service";
|
||||
import { StateMigrationService } from "@/jslib/common/src/abstractions/stateMigration.service";
|
||||
import { StorageService } from "@/jslib/common/src/abstractions/storage.service";
|
||||
|
||||
import { DirectoryType } from "@/src/enums/directoryType";
|
||||
import { EntraIdConfiguration } from "@/src/models/entraIdConfiguration";
|
||||
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 { StorageKeysVNext as StorageKeys, StoredSecurely } from "@/src/models/state.model";
|
||||
import { SyncConfiguration } from "@/src/models/syncConfiguration";
|
||||
|
||||
import { StateServiceVNextImplementation } from "./state-vNext.service";
|
||||
|
||||
describe("StateServiceVNextImplementation", () => {
|
||||
let storageService: MockProxy<StorageService>;
|
||||
let secureStorageService: MockProxy<StorageService>;
|
||||
let logService: MockProxy<LogService>;
|
||||
let stateMigrationService: MockProxy<StateMigrationService>;
|
||||
let stateService: StateServiceVNextImplementation;
|
||||
|
||||
beforeEach(() => {
|
||||
storageService = mock<StorageService>();
|
||||
secureStorageService = mock<StorageService>();
|
||||
logService = mock<LogService>();
|
||||
stateMigrationService = mock<StateMigrationService>();
|
||||
|
||||
stateService = new StateServiceVNextImplementation(
|
||||
storageService,
|
||||
secureStorageService,
|
||||
logService,
|
||||
stateMigrationService,
|
||||
true, // useSecureStorageForSecrets
|
||||
);
|
||||
});
|
||||
|
||||
describe("init", () => {
|
||||
it("should run migration if needed", async () => {
|
||||
stateMigrationService.needsMigration.mockResolvedValue(true);
|
||||
|
||||
await stateService.init();
|
||||
|
||||
expect(stateMigrationService.needsMigration).toHaveBeenCalled();
|
||||
expect(stateMigrationService.migrate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should not run migration if not needed", async () => {
|
||||
stateMigrationService.needsMigration.mockResolvedValue(false);
|
||||
|
||||
await stateService.init();
|
||||
|
||||
expect(stateMigrationService.needsMigration).toHaveBeenCalled();
|
||||
expect(stateMigrationService.migrate).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("clean", () => {
|
||||
it("should clear all directory settings and configurations", async () => {
|
||||
await stateService.clean();
|
||||
|
||||
// Verify all directory types are cleared
|
||||
expect(storageService.save).toHaveBeenCalledWith(StorageKeys.directoryType, null);
|
||||
expect(storageService.save).toHaveBeenCalledWith(StorageKeys.organizationId, null);
|
||||
expect(storageService.save).toHaveBeenCalledWith(StorageKeys.sync, null);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Directory Type", () => {
|
||||
it("should store and retrieve directory type", async () => {
|
||||
storageService.get.mockResolvedValue(DirectoryType.Ldap);
|
||||
|
||||
await stateService.setDirectoryType(DirectoryType.Ldap);
|
||||
const result = await stateService.getDirectoryType();
|
||||
|
||||
expect(storageService.save).toHaveBeenCalledWith(
|
||||
StorageKeys.directoryType,
|
||||
DirectoryType.Ldap,
|
||||
);
|
||||
expect(result).toBe(DirectoryType.Ldap);
|
||||
});
|
||||
|
||||
it("should return null when directory type is not set", async () => {
|
||||
storageService.get.mockResolvedValue(null);
|
||||
|
||||
const result = await stateService.getDirectoryType();
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Organization Id", () => {
|
||||
it("should store and retrieve organization ID", async () => {
|
||||
const orgId = "test-org-123";
|
||||
|
||||
storageService.get.mockResolvedValue(orgId);
|
||||
|
||||
await stateService.setOrganizationId(orgId);
|
||||
const result = await stateService.getOrganizationId();
|
||||
|
||||
expect(storageService.save).toHaveBeenCalledWith(StorageKeys.organizationId, orgId);
|
||||
expect(result).toBe(orgId);
|
||||
});
|
||||
});
|
||||
|
||||
describe("LDAP Configuration", () => {
|
||||
it("should store and retrieve LDAP configuration with secrets in secure storage", async () => {
|
||||
const config: LdapConfiguration = {
|
||||
ssl: true,
|
||||
startTls: false,
|
||||
tlsCaPath: null,
|
||||
sslAllowUnauthorized: false,
|
||||
sslCertPath: null,
|
||||
sslKeyPath: null,
|
||||
sslCaPath: null,
|
||||
hostname: "ldap.example.com",
|
||||
port: 636,
|
||||
domain: null,
|
||||
rootPath: null,
|
||||
ad: true,
|
||||
username: "admin",
|
||||
password: "secret-password",
|
||||
currentUser: false,
|
||||
pagedSearch: true,
|
||||
};
|
||||
|
||||
secureStorageService.get.mockResolvedValue("secret-password");
|
||||
storageService.get.mockResolvedValue({
|
||||
...config,
|
||||
password: StoredSecurely,
|
||||
});
|
||||
|
||||
await stateService.setDirectory(DirectoryType.Ldap, config);
|
||||
const result = await stateService.getDirectory<LdapConfiguration>(DirectoryType.Ldap);
|
||||
|
||||
// Verify password is stored in secure storage
|
||||
expect(secureStorageService.save).toHaveBeenCalled();
|
||||
|
||||
// Verify configuration is stored
|
||||
expect(storageService.save).toHaveBeenCalled();
|
||||
|
||||
// Verify retrieved config has real password from secure storage
|
||||
expect(result?.password).toBe("secret-password");
|
||||
});
|
||||
|
||||
it("should return null when LDAP configuration is not set", async () => {
|
||||
storageService.get.mockResolvedValue(null);
|
||||
|
||||
const result = await stateService.getLdapConfiguration();
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it("should handle null password in LDAP configuration", async () => {
|
||||
const config: LdapConfiguration = {
|
||||
ssl: true,
|
||||
startTls: false,
|
||||
tlsCaPath: null,
|
||||
sslAllowUnauthorized: false,
|
||||
sslCertPath: null,
|
||||
sslKeyPath: null,
|
||||
sslCaPath: null,
|
||||
hostname: "ldap.example.com",
|
||||
port: 636,
|
||||
domain: null,
|
||||
rootPath: null,
|
||||
ad: true,
|
||||
username: "admin",
|
||||
password: null,
|
||||
currentUser: false,
|
||||
pagedSearch: true,
|
||||
};
|
||||
|
||||
await stateService.setDirectory(DirectoryType.Ldap, config);
|
||||
|
||||
// Null passwords should call remove on the secure storage secret key
|
||||
expect(secureStorageService.remove).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("GSuite Configuration", () => {
|
||||
it("should store and retrieve GSuite configuration with privateKey in secure storage", async () => {
|
||||
const config: GSuiteConfiguration = {
|
||||
domain: "example.com",
|
||||
clientEmail: "service@example.com",
|
||||
adminUser: "admin@example.com",
|
||||
privateKey: "private-key-content",
|
||||
customer: "customer-id",
|
||||
};
|
||||
|
||||
secureStorageService.get.mockResolvedValue("private-key-content");
|
||||
storageService.get.mockResolvedValue({
|
||||
...config,
|
||||
privateKey: StoredSecurely,
|
||||
});
|
||||
|
||||
await stateService.setDirectory(DirectoryType.GSuite, config);
|
||||
const result = await stateService.getDirectory<GSuiteConfiguration>(DirectoryType.GSuite);
|
||||
|
||||
expect(secureStorageService.save).toHaveBeenCalled();
|
||||
expect(result?.privateKey).toBe("private-key-content");
|
||||
});
|
||||
|
||||
it("should handle null privateKey in GSuite configuration", async () => {
|
||||
const config: GSuiteConfiguration = {
|
||||
domain: "example.com",
|
||||
clientEmail: "service@example.com",
|
||||
adminUser: "admin@example.com",
|
||||
privateKey: null,
|
||||
customer: "customer-id",
|
||||
};
|
||||
|
||||
await stateService.setDirectory(DirectoryType.GSuite, config);
|
||||
|
||||
// Null privateKey should call remove on the secure storage secret key
|
||||
expect(secureStorageService.remove).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Entra ID Configuration", () => {
|
||||
it("should store and retrieve Entra ID configuration with key in secure storage", async () => {
|
||||
const config: EntraIdConfiguration = {
|
||||
identityAuthority: "https://login.microsoftonline.com",
|
||||
tenant: "tenant-id",
|
||||
applicationId: "app-id",
|
||||
key: "secret-key",
|
||||
};
|
||||
|
||||
secureStorageService.get.mockResolvedValue("secret-key");
|
||||
storageService.get.mockResolvedValue({
|
||||
...config,
|
||||
key: StoredSecurely,
|
||||
});
|
||||
|
||||
await stateService.setDirectory(DirectoryType.EntraID, config);
|
||||
const result = await stateService.getDirectory<EntraIdConfiguration>(DirectoryType.EntraID);
|
||||
|
||||
expect(secureStorageService.save).toHaveBeenCalled();
|
||||
expect(result?.key).toBe("secret-key");
|
||||
});
|
||||
|
||||
it("should maintain backwards compatibility with Azure key storage", async () => {
|
||||
const config: EntraIdConfiguration = {
|
||||
identityAuthority: "https://login.microsoftonline.com",
|
||||
tenant: "tenant-id",
|
||||
applicationId: "app-id",
|
||||
key: StoredSecurely,
|
||||
};
|
||||
|
||||
storageService.get.mockResolvedValue(config);
|
||||
secureStorageService.get.mockResolvedValueOnce(null); // entra key not found
|
||||
secureStorageService.get.mockResolvedValueOnce("azure-secret-key"); // fallback to azure key
|
||||
|
||||
const result = await stateService.getDirectory<EntraIdConfiguration>(DirectoryType.EntraID);
|
||||
|
||||
expect(secureStorageService.get).toHaveBeenCalled();
|
||||
expect(result?.key).toBe("azure-secret-key");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Okta Configuration", () => {
|
||||
it("should store and retrieve Okta configuration with token in secure storage", async () => {
|
||||
const config: OktaConfiguration = {
|
||||
orgUrl: "https://example.okta.com",
|
||||
token: "okta-token",
|
||||
};
|
||||
|
||||
secureStorageService.get.mockResolvedValue("okta-token");
|
||||
storageService.get.mockResolvedValue({
|
||||
...config,
|
||||
token: StoredSecurely,
|
||||
});
|
||||
|
||||
await stateService.setDirectory(DirectoryType.Okta, config);
|
||||
const result = await stateService.getDirectory<OktaConfiguration>(DirectoryType.Okta);
|
||||
|
||||
expect(secureStorageService.save).toHaveBeenCalled();
|
||||
expect(result?.token).toBe("okta-token");
|
||||
});
|
||||
});
|
||||
|
||||
describe("OneLogin Configuration", () => {
|
||||
it("should store and retrieve OneLogin configuration with clientSecret in secure storage", async () => {
|
||||
const config: OneLoginConfiguration = {
|
||||
region: "us",
|
||||
clientId: "client-id",
|
||||
clientSecret: "client-secret",
|
||||
};
|
||||
|
||||
secureStorageService.get.mockResolvedValue("client-secret");
|
||||
storageService.get.mockResolvedValue({
|
||||
...config,
|
||||
clientSecret: StoredSecurely,
|
||||
});
|
||||
|
||||
await stateService.setDirectory(DirectoryType.OneLogin, config);
|
||||
const result = await stateService.getDirectory<OneLoginConfiguration>(DirectoryType.OneLogin);
|
||||
|
||||
expect(secureStorageService.save).toHaveBeenCalled();
|
||||
expect(result?.clientSecret).toBe("client-secret");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Sync Configuration", () => {
|
||||
it("should store and retrieve sync configuration", async () => {
|
||||
const syncConfig: SyncConfiguration = {
|
||||
users: true,
|
||||
groups: true,
|
||||
interval: 5,
|
||||
userFilter: null,
|
||||
groupFilter: null,
|
||||
removeDisabled: true,
|
||||
overwriteExisting: false,
|
||||
largeImport: false,
|
||||
groupObjectClass: null,
|
||||
userObjectClass: null,
|
||||
groupPath: null,
|
||||
userPath: null,
|
||||
groupNameAttribute: null,
|
||||
userEmailAttribute: null,
|
||||
memberAttribute: "member",
|
||||
creationDateAttribute: "whenCreated",
|
||||
revisionDateAttribute: "whenChanged",
|
||||
useEmailPrefixSuffix: false,
|
||||
emailPrefixAttribute: null,
|
||||
emailSuffix: null,
|
||||
};
|
||||
|
||||
storageService.get.mockResolvedValue(syncConfig);
|
||||
|
||||
await stateService.setSync(syncConfig);
|
||||
const result = await stateService.getSync();
|
||||
|
||||
expect(storageService.save).toHaveBeenCalledWith(StorageKeys.sync, syncConfig);
|
||||
expect(result).toEqual(syncConfig);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Sync Settings", () => {
|
||||
it("should clear sync settings when clearSyncSettings is called", async () => {
|
||||
await stateService.clearSyncSettings(false);
|
||||
|
||||
// Should set delta and sync values to null
|
||||
expect(storageService.save).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should clear lastSyncHash when hashToo is true", async () => {
|
||||
await stateService.clearSyncSettings(true);
|
||||
|
||||
// Should set all values including lastSyncHash to null
|
||||
expect(storageService.save).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should not clear lastSyncHash when hashToo is false", async () => {
|
||||
await stateService.clearSyncSettings(false);
|
||||
|
||||
// Should set delta and sync values but not lastSyncHash
|
||||
expect(storageService.save).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Last Sync Hash", () => {
|
||||
it("should store and retrieve last sync hash", async () => {
|
||||
const hash = "hash";
|
||||
|
||||
storageService.get.mockResolvedValue(hash);
|
||||
|
||||
await stateService.setLastSyncHash(hash);
|
||||
const result = await stateService.getLastSyncHash();
|
||||
|
||||
expect(storageService.save).toHaveBeenCalled();
|
||||
expect(result).toBe(hash);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Delta Tokens", () => {
|
||||
it("should store and retrieve user delta token", async () => {
|
||||
const token = "user-delta-token";
|
||||
|
||||
storageService.get.mockResolvedValue(token);
|
||||
|
||||
await stateService.setUserDelta(token);
|
||||
const result = await stateService.getUserDelta();
|
||||
|
||||
expect(storageService.save).toHaveBeenCalled();
|
||||
expect(result).toBe(token);
|
||||
});
|
||||
|
||||
it("should store and retrieve group delta token", async () => {
|
||||
const token = "group-delta-token";
|
||||
|
||||
storageService.get.mockResolvedValue(token);
|
||||
|
||||
await stateService.setGroupDelta(token);
|
||||
const result = await stateService.getGroupDelta();
|
||||
|
||||
expect(storageService.save).toHaveBeenCalled();
|
||||
expect(result).toBe(token);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Last Sync Timestamps", () => {
|
||||
it("should store and retrieve last user sync timestamp", async () => {
|
||||
const timestamp = new Date("2024-01-01T00:00:00Z");
|
||||
|
||||
storageService.get.mockResolvedValue(timestamp.toISOString());
|
||||
|
||||
await stateService.setLastUserSync(timestamp);
|
||||
const result = await stateService.getLastUserSync();
|
||||
|
||||
expect(storageService.save).toHaveBeenCalled();
|
||||
expect(result?.toISOString()).toBe(timestamp.toISOString());
|
||||
});
|
||||
|
||||
it("should store and retrieve last group sync timestamp", async () => {
|
||||
const timestamp = new Date("2024-01-01T00:00:00Z");
|
||||
|
||||
storageService.get.mockResolvedValue(timestamp.toISOString());
|
||||
|
||||
await stateService.setLastGroupSync(timestamp);
|
||||
const result = await stateService.getLastGroupSync();
|
||||
|
||||
expect(storageService.save).toHaveBeenCalled();
|
||||
expect(result?.toISOString()).toBe(timestamp.toISOString());
|
||||
});
|
||||
|
||||
it("should return null when last user sync timestamp is not set", async () => {
|
||||
storageService.get.mockResolvedValue(null);
|
||||
|
||||
const result = await stateService.getLastUserSync();
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it("should return null when last group sync timestamp is not set", async () => {
|
||||
storageService.get.mockResolvedValue(null);
|
||||
|
||||
const result = await stateService.getLastGroupSync();
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Secure Storage Flag", () => {
|
||||
it("should not separate secrets when useSecureStorageForSecrets is false", async () => {
|
||||
const insecureStateService = new StateServiceVNextImplementation(
|
||||
storageService,
|
||||
secureStorageService,
|
||||
logService,
|
||||
stateMigrationService,
|
||||
false, // useSecureStorageForSecrets = false
|
||||
);
|
||||
|
||||
const config: LdapConfiguration = {
|
||||
ssl: true,
|
||||
startTls: false,
|
||||
tlsCaPath: null,
|
||||
sslAllowUnauthorized: false,
|
||||
sslCertPath: null,
|
||||
sslKeyPath: null,
|
||||
sslCaPath: null,
|
||||
hostname: "ldap.example.com",
|
||||
port: 636,
|
||||
domain: null,
|
||||
rootPath: null,
|
||||
ad: true,
|
||||
username: "admin",
|
||||
password: "secret-password",
|
||||
currentUser: false,
|
||||
pagedSearch: true,
|
||||
};
|
||||
|
||||
storageService.get.mockResolvedValue(config);
|
||||
|
||||
// When useSecureStorageForSecrets is false, setDirectory doesn't process secrets
|
||||
await insecureStateService.setDirectory(DirectoryType.Ldap, config);
|
||||
|
||||
// Retrieve config - should return password as-is from storage (not from secure storage)
|
||||
const result = await insecureStateService.getDirectory<LdapConfiguration>(DirectoryType.Ldap);
|
||||
|
||||
// Password should be retrieved directly from storage, not secure storage
|
||||
expect(result?.password).toBe("secret-password");
|
||||
expect(secureStorageService.get).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
409
src/services/state-service/state-vNext.service.ts
Normal file
409
src/services/state-service/state-vNext.service.ts
Normal file
@@ -0,0 +1,409 @@
|
||||
import { LogService } from "@/jslib/common/src/abstractions/log.service";
|
||||
import { StateMigrationService } from "@/jslib/common/src/abstractions/stateMigration.service";
|
||||
import { StorageService } from "@/jslib/common/src/abstractions/storage.service";
|
||||
import { EnvironmentUrls } from "@/jslib/common/src/models/domain/environmentUrls";
|
||||
import { StorageOptions } from "@/jslib/common/src/models/domain/storageOptions";
|
||||
|
||||
import { StateServiceVNext as StateServiceVNextAbstraction } from "@/src/abstractions/state-vNext.service";
|
||||
import { DirectoryType } from "@/src/enums/directoryType";
|
||||
import { IConfiguration } from "@/src/models/IConfiguration";
|
||||
import { EntraIdConfiguration } from "@/src/models/entraIdConfiguration";
|
||||
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 {
|
||||
SecureStorageKeysVNext as SecureStorageKeys,
|
||||
StorageKeysVNext as StorageKeys,
|
||||
StoredSecurely,
|
||||
} from "@/src/models/state.model";
|
||||
import { SyncConfiguration } from "@/src/models/syncConfiguration";
|
||||
|
||||
export class StateServiceVNextImplementation implements StateServiceVNextAbstraction {
|
||||
constructor(
|
||||
protected storageService: StorageService,
|
||||
protected secureStorageService: StorageService,
|
||||
protected logService: LogService,
|
||||
protected stateMigrationService: StateMigrationService,
|
||||
private useSecureStorageForSecrets = true,
|
||||
) {}
|
||||
|
||||
async init(): Promise<void> {
|
||||
if (await this.stateMigrationService.needsMigration()) {
|
||||
await this.stateMigrationService.migrate();
|
||||
}
|
||||
}
|
||||
|
||||
async clean(options?: StorageOptions): Promise<void> {
|
||||
// Clear all directory settings and configurations
|
||||
// but preserve version and environment settings
|
||||
await this.setDirectoryType(null);
|
||||
await this.setOrganizationId(null);
|
||||
await this.setSync(null);
|
||||
await this.setLdapConfiguration(null);
|
||||
await this.setGsuiteConfiguration(null);
|
||||
await this.setEntraConfiguration(null);
|
||||
await this.setOktaConfiguration(null);
|
||||
await this.setOneLoginConfiguration(null);
|
||||
await this.clearSyncSettings(true);
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// Directory Configuration Methods
|
||||
// ===================================================================
|
||||
|
||||
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) {
|
||||
// Create a copy to avoid modifying the cached config
|
||||
const configWithSecrets = Object.assign({}, config);
|
||||
|
||||
switch (type) {
|
||||
case DirectoryType.Ldap:
|
||||
(configWithSecrets as any).password = await this.getLdapSecret();
|
||||
break;
|
||||
case DirectoryType.EntraID:
|
||||
(configWithSecrets as any).key = await this.getEntraSecret();
|
||||
break;
|
||||
case DirectoryType.Okta:
|
||||
(configWithSecrets as any).token = await this.getOktaSecret();
|
||||
break;
|
||||
case DirectoryType.GSuite:
|
||||
(configWithSecrets as any).privateKey = await this.getGsuiteSecret();
|
||||
break;
|
||||
case DirectoryType.OneLogin:
|
||||
(configWithSecrets as any).clientSecret = await this.getOneLoginSecret();
|
||||
break;
|
||||
}
|
||||
|
||||
return configWithSecrets as T;
|
||||
}
|
||||
|
||||
return config as T;
|
||||
}
|
||||
|
||||
async setDirectory(
|
||||
type: DirectoryType,
|
||||
config:
|
||||
| LdapConfiguration
|
||||
| GSuiteConfiguration
|
||||
| EntraIdConfiguration
|
||||
| OktaConfiguration
|
||||
| OneLoginConfiguration,
|
||||
): Promise<any> {
|
||||
if (this.useSecureStorageForSecrets) {
|
||||
switch (type) {
|
||||
case DirectoryType.Ldap: {
|
||||
const ldapConfig = config as LdapConfiguration;
|
||||
await this.setLdapSecret(ldapConfig.password);
|
||||
ldapConfig.password = StoredSecurely;
|
||||
await this.setLdapConfiguration(ldapConfig);
|
||||
break;
|
||||
}
|
||||
case DirectoryType.EntraID: {
|
||||
const entraConfig = config as EntraIdConfiguration;
|
||||
await this.setEntraSecret(entraConfig.key);
|
||||
entraConfig.key = StoredSecurely;
|
||||
await this.setEntraConfiguration(entraConfig);
|
||||
break;
|
||||
}
|
||||
case DirectoryType.Okta: {
|
||||
const oktaConfig = config as OktaConfiguration;
|
||||
await this.setOktaSecret(oktaConfig.token);
|
||||
oktaConfig.token = StoredSecurely;
|
||||
await this.setOktaConfiguration(oktaConfig);
|
||||
break;
|
||||
}
|
||||
case DirectoryType.GSuite: {
|
||||
const gsuiteConfig = config as GSuiteConfiguration;
|
||||
if (gsuiteConfig.privateKey == null) {
|
||||
await this.setGsuiteSecret(null);
|
||||
} else {
|
||||
const normalizedPrivateKey = gsuiteConfig.privateKey.replace(/\\n/g, "\n");
|
||||
await this.setGsuiteSecret(normalizedPrivateKey);
|
||||
gsuiteConfig.privateKey = StoredSecurely;
|
||||
}
|
||||
await this.setGsuiteConfiguration(gsuiteConfig);
|
||||
break;
|
||||
}
|
||||
case DirectoryType.OneLogin: {
|
||||
const oneLoginConfig = config as OneLoginConfiguration;
|
||||
await this.setOneLoginSecret(oneLoginConfig.clientSecret);
|
||||
oneLoginConfig.clientSecret = StoredSecurely;
|
||||
await this.setOneLoginConfiguration(oneLoginConfig);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async getConfiguration(type: DirectoryType): Promise<IConfiguration> {
|
||||
switch (type) {
|
||||
case DirectoryType.Ldap:
|
||||
return await this.getLdapConfiguration();
|
||||
case DirectoryType.GSuite:
|
||||
return await this.getGsuiteConfiguration();
|
||||
case DirectoryType.EntraID:
|
||||
return await this.getEntraConfiguration();
|
||||
case DirectoryType.Okta:
|
||||
return await this.getOktaConfiguration();
|
||||
case DirectoryType.OneLogin:
|
||||
return await this.getOneLoginConfiguration();
|
||||
}
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// Secret Storage Methods (Secure Storage)
|
||||
// ===================================================================
|
||||
|
||||
private async getLdapSecret(): Promise<string> {
|
||||
return await this.secureStorageService.get<string>(SecureStorageKeys.ldap);
|
||||
}
|
||||
|
||||
private async setLdapSecret(value: string): Promise<void> {
|
||||
if (value == null) {
|
||||
await this.secureStorageService.remove(SecureStorageKeys.ldap);
|
||||
} else {
|
||||
await this.secureStorageService.save(SecureStorageKeys.ldap, value);
|
||||
}
|
||||
}
|
||||
|
||||
private async getGsuiteSecret(): Promise<string> {
|
||||
return await this.secureStorageService.get<string>(SecureStorageKeys.gsuite);
|
||||
}
|
||||
|
||||
private async setGsuiteSecret(value: string): Promise<void> {
|
||||
if (value == null) {
|
||||
await this.secureStorageService.remove(SecureStorageKeys.gsuite);
|
||||
} else {
|
||||
await this.secureStorageService.save(SecureStorageKeys.gsuite, value);
|
||||
}
|
||||
}
|
||||
|
||||
private async getEntraSecret(): Promise<string> {
|
||||
// Try new key first, fall back to old azure key for backwards compatibility
|
||||
const entraKey = await this.secureStorageService.get<string>(SecureStorageKeys.entra);
|
||||
if (entraKey != null) {
|
||||
return entraKey;
|
||||
}
|
||||
return await this.secureStorageService.get<string>(SecureStorageKeys.azure);
|
||||
}
|
||||
|
||||
private async setEntraSecret(value: string): Promise<void> {
|
||||
if (value == null) {
|
||||
await this.secureStorageService.remove(SecureStorageKeys.entra);
|
||||
await this.secureStorageService.remove(SecureStorageKeys.azure);
|
||||
} else {
|
||||
await this.secureStorageService.save(SecureStorageKeys.entra, value);
|
||||
}
|
||||
}
|
||||
|
||||
private async getOktaSecret(): Promise<string> {
|
||||
return await this.secureStorageService.get<string>(SecureStorageKeys.okta);
|
||||
}
|
||||
|
||||
private async setOktaSecret(value: string): Promise<void> {
|
||||
if (value == null) {
|
||||
await this.secureStorageService.remove(SecureStorageKeys.okta);
|
||||
} else {
|
||||
await this.secureStorageService.save(SecureStorageKeys.okta, value);
|
||||
}
|
||||
}
|
||||
|
||||
private async getOneLoginSecret(): Promise<string> {
|
||||
return await this.secureStorageService.get<string>(SecureStorageKeys.oneLogin);
|
||||
}
|
||||
|
||||
private async setOneLoginSecret(value: string): Promise<void> {
|
||||
if (value == null) {
|
||||
await this.secureStorageService.remove(SecureStorageKeys.oneLogin);
|
||||
} else {
|
||||
await this.secureStorageService.save(SecureStorageKeys.oneLogin, value);
|
||||
}
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// Directory-Specific Configuration Methods
|
||||
// ===================================================================
|
||||
|
||||
async getLdapConfiguration(options?: StorageOptions): Promise<LdapConfiguration> {
|
||||
return await this.storageService.get<LdapConfiguration>(StorageKeys.directory_ldap);
|
||||
}
|
||||
|
||||
async setLdapConfiguration(value: LdapConfiguration, options?: StorageOptions): Promise<void> {
|
||||
await this.storageService.save(StorageKeys.directory_ldap, value);
|
||||
}
|
||||
|
||||
async getGsuiteConfiguration(options?: StorageOptions): Promise<GSuiteConfiguration> {
|
||||
return await this.storageService.get<GSuiteConfiguration>(StorageKeys.directory_gsuite);
|
||||
}
|
||||
|
||||
async setGsuiteConfiguration(
|
||||
value: GSuiteConfiguration,
|
||||
options?: StorageOptions,
|
||||
): Promise<void> {
|
||||
await this.storageService.save(StorageKeys.directory_gsuite, value);
|
||||
}
|
||||
|
||||
async getEntraConfiguration(options?: StorageOptions): Promise<EntraIdConfiguration> {
|
||||
return await this.storageService.get<EntraIdConfiguration>(StorageKeys.directory_entra);
|
||||
}
|
||||
|
||||
async setEntraConfiguration(
|
||||
value: EntraIdConfiguration,
|
||||
options?: StorageOptions,
|
||||
): Promise<void> {
|
||||
await this.storageService.save(StorageKeys.directory_entra, value);
|
||||
}
|
||||
|
||||
async getOktaConfiguration(options?: StorageOptions): Promise<OktaConfiguration> {
|
||||
return await this.storageService.get<OktaConfiguration>(StorageKeys.directory_okta);
|
||||
}
|
||||
|
||||
async setOktaConfiguration(value: OktaConfiguration, options?: StorageOptions): Promise<void> {
|
||||
await this.storageService.save(StorageKeys.directory_okta, value);
|
||||
}
|
||||
|
||||
async getOneLoginConfiguration(options?: StorageOptions): Promise<OneLoginConfiguration> {
|
||||
return await this.storageService.get<OneLoginConfiguration>(StorageKeys.directory_onelogin);
|
||||
}
|
||||
|
||||
async setOneLoginConfiguration(
|
||||
value: OneLoginConfiguration,
|
||||
options?: StorageOptions,
|
||||
): Promise<void> {
|
||||
await this.storageService.save(StorageKeys.directory_onelogin, value);
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// Directory Settings Methods
|
||||
// ===================================================================
|
||||
|
||||
async getOrganizationId(options?: StorageOptions): Promise<string> {
|
||||
return await this.storageService.get<string>(StorageKeys.organizationId);
|
||||
}
|
||||
|
||||
async setOrganizationId(value: string, options?: StorageOptions): Promise<void> {
|
||||
const currentId = await this.getOrganizationId();
|
||||
if (currentId !== value) {
|
||||
await this.clearSyncSettings();
|
||||
}
|
||||
await this.storageService.save(StorageKeys.organizationId, value);
|
||||
}
|
||||
|
||||
async getSync(options?: StorageOptions): Promise<SyncConfiguration> {
|
||||
return await this.storageService.get<SyncConfiguration>(StorageKeys.sync);
|
||||
}
|
||||
|
||||
async setSync(value: SyncConfiguration, options?: StorageOptions): Promise<void> {
|
||||
await this.storageService.save(StorageKeys.sync, value);
|
||||
}
|
||||
|
||||
async getDirectoryType(options?: StorageOptions): Promise<DirectoryType> {
|
||||
return await this.storageService.get<DirectoryType>(StorageKeys.directoryType);
|
||||
}
|
||||
|
||||
async setDirectoryType(value: DirectoryType, options?: StorageOptions): Promise<void> {
|
||||
const currentType = await this.getDirectoryType();
|
||||
if (value !== currentType) {
|
||||
await this.clearSyncSettings();
|
||||
}
|
||||
await this.storageService.save(StorageKeys.directoryType, value);
|
||||
}
|
||||
|
||||
async getLastUserSync(options?: StorageOptions): Promise<Date> {
|
||||
const dateString = await this.storageService.get<string>(SecureStorageKeys.lastUserSync);
|
||||
return dateString ? new Date(dateString) : null;
|
||||
}
|
||||
|
||||
async setLastUserSync(value: Date, options?: StorageOptions): Promise<void> {
|
||||
await this.storageService.save(SecureStorageKeys.lastUserSync, value);
|
||||
}
|
||||
|
||||
async getLastGroupSync(options?: StorageOptions): Promise<Date> {
|
||||
const dateString = await this.storageService.get<string>(SecureStorageKeys.lastGroupSync);
|
||||
return dateString ? new Date(dateString) : null;
|
||||
}
|
||||
|
||||
async setLastGroupSync(value: Date, options?: StorageOptions): Promise<void> {
|
||||
await this.storageService.save(SecureStorageKeys.lastGroupSync, value);
|
||||
}
|
||||
|
||||
async getLastSyncHash(options?: StorageOptions): Promise<string> {
|
||||
return await this.storageService.get<string>(SecureStorageKeys.lastSyncHash);
|
||||
}
|
||||
|
||||
async setLastSyncHash(value: string, options?: StorageOptions): Promise<void> {
|
||||
await this.storageService.save(SecureStorageKeys.lastSyncHash, value);
|
||||
}
|
||||
|
||||
async getSyncingDir(options?: StorageOptions): Promise<boolean> {
|
||||
return await this.storageService.get<boolean>(StorageKeys.syncingDir);
|
||||
}
|
||||
|
||||
async setSyncingDir(value: boolean, options?: StorageOptions): Promise<void> {
|
||||
await this.storageService.save(StorageKeys.syncingDir, value);
|
||||
}
|
||||
|
||||
async getUserDelta(options?: StorageOptions): Promise<string> {
|
||||
return await this.storageService.get<string>(SecureStorageKeys.userDelta);
|
||||
}
|
||||
|
||||
async setUserDelta(value: string, options?: StorageOptions): Promise<void> {
|
||||
await this.storageService.save(SecureStorageKeys.userDelta, value);
|
||||
}
|
||||
|
||||
async getGroupDelta(options?: StorageOptions): Promise<string> {
|
||||
return await this.storageService.get<string>(SecureStorageKeys.groupDelta);
|
||||
}
|
||||
|
||||
async setGroupDelta(value: string, options?: StorageOptions): Promise<void> {
|
||||
await this.storageService.save(SecureStorageKeys.groupDelta, value);
|
||||
}
|
||||
|
||||
async clearSyncSettings(hashToo = false): Promise<void> {
|
||||
await this.setUserDelta(null);
|
||||
await this.setGroupDelta(null);
|
||||
await this.setLastGroupSync(null);
|
||||
await this.setLastUserSync(null);
|
||||
if (hashToo) {
|
||||
await this.setLastSyncHash(null);
|
||||
}
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// Environment URLs (inherited from base, simplified implementation)
|
||||
// ===================================================================
|
||||
|
||||
async getEnvironmentUrls(options?: StorageOptions): Promise<EnvironmentUrls> {
|
||||
return await this.storageService.get<EnvironmentUrls>("environmentUrls");
|
||||
}
|
||||
|
||||
async setEnvironmentUrls(value: EnvironmentUrls): Promise<void> {
|
||||
await this.storageService.save("environmentUrls", value);
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// Additional State Methods
|
||||
// ===================================================================
|
||||
|
||||
async getLocale(options?: StorageOptions): Promise<string> {
|
||||
return await this.storageService.get<string>("locale");
|
||||
}
|
||||
|
||||
async setLocale(value: string, options?: StorageOptions): Promise<void> {
|
||||
await this.storageService.save("locale", value);
|
||||
}
|
||||
|
||||
async getInstalledVersion(options?: StorageOptions): Promise<string> {
|
||||
return await this.storageService.get<string>("installedVersion");
|
||||
}
|
||||
|
||||
async setInstalledVersion(value: string, options?: StorageOptions): Promise<void> {
|
||||
await this.storageService.save("installedVersion", value);
|
||||
}
|
||||
}
|
||||
@@ -16,32 +16,13 @@ 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 {
|
||||
SecureStorageKeysLegacy as SecureStorageKeys,
|
||||
StoredSecurely,
|
||||
TempKeys as keys,
|
||||
} from "@/src/models/state.model";
|
||||
import { SyncConfiguration } from "@/src/models/syncConfiguration";
|
||||
|
||||
const SecureStorageKeys = {
|
||||
ldap: "ldapPassword",
|
||||
gsuite: "gsuitePrivateKey",
|
||||
// Azure Active Directory was renamed to Entra ID, but we've kept the old property name
|
||||
// to be backwards compatible with existing configurations.
|
||||
azure: "azureKey",
|
||||
entra: "entraKey",
|
||||
okta: "oktaToken",
|
||||
oneLogin: "oneLoginClientSecret",
|
||||
userDelta: "userDeltaToken",
|
||||
groupDelta: "groupDeltaToken",
|
||||
lastUserSync: "lastUserSync",
|
||||
lastGroupSync: "lastGroupSync",
|
||||
lastSyncHash: "lastSyncHash",
|
||||
};
|
||||
|
||||
const keys = {
|
||||
tempAccountSettings: "tempAccountSettings",
|
||||
tempDirectoryConfigs: "tempDirectoryConfigs",
|
||||
tempDirectorySettings: "tempDirectorySettings",
|
||||
};
|
||||
|
||||
const StoredSecurely = "[STORED SECURELY]";
|
||||
|
||||
export class StateService
|
||||
extends BaseStateService<GlobalState, Account>
|
||||
implements StateServiceAbstraction
|
||||
@@ -8,48 +8,14 @@ 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 {
|
||||
MigrationClientKeys as ClientKeys,
|
||||
MigrationKeys as Keys,
|
||||
MigrationStateKeys as StateKeys,
|
||||
SecureStorageKeysMigration as SecureStorageKeys,
|
||||
} from "@/src/models/state.model";
|
||||
import { SyncConfiguration } from "@/src/models/syncConfiguration";
|
||||
|
||||
const SecureStorageKeys: { [key: string]: any } = {
|
||||
ldap: "ldapPassword",
|
||||
gsuite: "gsuitePrivateKey",
|
||||
azure: "azureKey",
|
||||
entra: "entraIdKey",
|
||||
okta: "oktaToken",
|
||||
oneLogin: "oneLoginClientSecret",
|
||||
directoryConfigPrefix: "directoryConfig_",
|
||||
sync: "syncConfig",
|
||||
directoryType: "directoryType",
|
||||
organizationId: "organizationId",
|
||||
};
|
||||
|
||||
const Keys: { [key: string]: any } = {
|
||||
entityId: "entityId",
|
||||
directoryType: "directoryType",
|
||||
organizationId: "organizationId",
|
||||
lastUserSync: "lastUserSync",
|
||||
lastGroupSync: "lastGroupSync",
|
||||
lastSyncHash: "lastSyncHash",
|
||||
syncingDir: "syncingDir",
|
||||
syncConfig: "syncConfig",
|
||||
userDelta: "userDeltaToken",
|
||||
groupDelta: "groupDeltaToken",
|
||||
tempDirectoryConfigs: "tempDirectoryConfigs",
|
||||
tempDirectorySettings: "tempDirectorySettings",
|
||||
};
|
||||
|
||||
const StateKeys = {
|
||||
global: "global",
|
||||
authenticatedAccounts: "authenticatedAccounts",
|
||||
};
|
||||
|
||||
const ClientKeys: { [key: string]: any } = {
|
||||
clientIdOld: "clientId",
|
||||
clientId: "apikey_clientId",
|
||||
clientSecretOld: "clientSecret",
|
||||
clientSecret: "apikey_clientSecret",
|
||||
};
|
||||
|
||||
export class StateMigrationService extends BaseStateMigrationService {
|
||||
async migrate(): Promise<void> {
|
||||
let currentStateVersion = await this.getCurrentStateVersion();
|
||||
@@ -61,6 +27,13 @@ export class StateMigrationService extends BaseStateMigrationService {
|
||||
break;
|
||||
case StateVersion.Two:
|
||||
await this.migrateStateFrom2To3();
|
||||
break;
|
||||
case StateVersion.Three:
|
||||
await this.migrateStateFrom3To4();
|
||||
break;
|
||||
case StateVersion.Four:
|
||||
await this.migrateStateFrom4To5();
|
||||
break;
|
||||
}
|
||||
currentStateVersion += 1;
|
||||
}
|
||||
@@ -198,4 +171,124 @@ export class StateMigrationService extends BaseStateMigrationService {
|
||||
globals.stateVersion = StateVersion.Three;
|
||||
await this.set(StateKeys.global, globals);
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate from State v4 (Account-based hierarchy) to v5 (flat key-value structure)
|
||||
*
|
||||
* This is a clean break from the Account-based structure. Data is extracted from
|
||||
* the account and saved into flat keys for simpler access.
|
||||
*
|
||||
* Old structure: authenticatedAccounts -> userId -> account.directorySettings/directoryConfigurations
|
||||
* New structure: flat keys like "directoryType", "organizationId", "directory_ldap", etc.
|
||||
*
|
||||
* Secrets migrate from: {userId}_{secretKey} -> secret_{secretKey}
|
||||
*/
|
||||
protected async migrateStateFrom4To5(useSecureStorageForSecrets = true): Promise<void> {
|
||||
// Get the authenticated user IDs from v3 structure
|
||||
const authenticatedUserIds = await this.get<string[]>(StateKeys.authenticatedAccounts);
|
||||
|
||||
if (
|
||||
!authenticatedUserIds ||
|
||||
!Array.isArray(authenticatedUserIds) ||
|
||||
authenticatedUserIds.length === 0
|
||||
) {
|
||||
// No accounts to migrate, just update version
|
||||
const globals = await this.getGlobals();
|
||||
globals.stateVersion = StateVersion.Five;
|
||||
await this.set(StateKeys.global, globals);
|
||||
return;
|
||||
}
|
||||
|
||||
// DC is single-user, so we take the first (and likely only) account
|
||||
const userId = authenticatedUserIds[0];
|
||||
const account = await this.get<Account>(userId);
|
||||
|
||||
if (!account) {
|
||||
// No account data found, just update version
|
||||
const globals = await this.getGlobals();
|
||||
globals.stateVersion = StateVersion.Five;
|
||||
await this.set(StateKeys.global, globals);
|
||||
return;
|
||||
}
|
||||
|
||||
// Migrate directory configurations to flat structure
|
||||
if (account.directoryConfigurations) {
|
||||
if (account.directoryConfigurations.ldap) {
|
||||
await this.set("directory_ldap", account.directoryConfigurations.ldap);
|
||||
}
|
||||
if (account.directoryConfigurations.gsuite) {
|
||||
await this.set("directory_gsuite", account.directoryConfigurations.gsuite);
|
||||
}
|
||||
if (account.directoryConfigurations.entra) {
|
||||
await this.set("directory_entra", account.directoryConfigurations.entra);
|
||||
} else if (account.directoryConfigurations.azure) {
|
||||
// Backwards compatibility: migrate azure to entra
|
||||
await this.set("directory_entra", account.directoryConfigurations.azure);
|
||||
}
|
||||
if (account.directoryConfigurations.okta) {
|
||||
await this.set("directory_okta", account.directoryConfigurations.okta);
|
||||
}
|
||||
if (account.directoryConfigurations.oneLogin) {
|
||||
await this.set("directory_onelogin", account.directoryConfigurations.oneLogin);
|
||||
}
|
||||
}
|
||||
|
||||
// Migrate directory settings to flat structure
|
||||
if (account.directorySettings) {
|
||||
if (account.directorySettings.organizationId) {
|
||||
await this.set("organizationId", account.directorySettings.organizationId);
|
||||
}
|
||||
if (account.directorySettings.directoryType != null) {
|
||||
await this.set("directoryType", account.directorySettings.directoryType);
|
||||
}
|
||||
if (account.directorySettings.sync) {
|
||||
await this.set("sync", account.directorySettings.sync);
|
||||
}
|
||||
if (account.directorySettings.lastUserSync) {
|
||||
await this.set("lastUserSync", account.directorySettings.lastUserSync);
|
||||
}
|
||||
if (account.directorySettings.lastGroupSync) {
|
||||
await this.set("lastGroupSync", account.directorySettings.lastGroupSync);
|
||||
}
|
||||
if (account.directorySettings.lastSyncHash) {
|
||||
await this.set("lastSyncHash", account.directorySettings.lastSyncHash);
|
||||
}
|
||||
if (account.directorySettings.userDelta) {
|
||||
await this.set("userDelta", account.directorySettings.userDelta);
|
||||
}
|
||||
if (account.directorySettings.groupDelta) {
|
||||
await this.set("groupDelta", account.directorySettings.groupDelta);
|
||||
}
|
||||
if (account.directorySettings.syncingDir != null) {
|
||||
await this.set("syncingDir", account.directorySettings.syncingDir);
|
||||
}
|
||||
}
|
||||
|
||||
// Migrate secrets from {userId}_* to secret_* pattern
|
||||
if (useSecureStorageForSecrets) {
|
||||
const oldSecretKeys = [
|
||||
{ old: `${userId}_${SecureStorageKeys.ldap}`, new: "secret_ldap" },
|
||||
{ old: `${userId}_${SecureStorageKeys.gsuite}`, new: "secret_gsuite" },
|
||||
{ old: `${userId}_${SecureStorageKeys.azure}`, new: "secret_azure" },
|
||||
{ old: `${userId}_${SecureStorageKeys.entra}`, new: "secret_entra" },
|
||||
{ old: `${userId}_${SecureStorageKeys.okta}`, new: "secret_okta" },
|
||||
{ old: `${userId}_${SecureStorageKeys.oneLogin}`, new: "secret_onelogin" },
|
||||
];
|
||||
|
||||
for (const { old: oldKey, new: newKey } of oldSecretKeys) {
|
||||
if (await this.secureStorageService.has(oldKey)) {
|
||||
const value = await this.secureStorageService.get(oldKey);
|
||||
if (value) {
|
||||
await this.secureStorageService.save(newKey, value);
|
||||
}
|
||||
// @TODO Keep old key for now - will remove in future release
|
||||
// await this.secureStorageService.remove(oldKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const globals = await this.getGlobals();
|
||||
globals.stateVersion = StateVersion.Five;
|
||||
await this.set(StateKeys.global, globals);
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ import { DirectoryType } from "../enums/directoryType";
|
||||
import { BatchRequestBuilder } from "./batch-request-builder";
|
||||
import { LdapDirectoryService } from "./directory-services/ldap-directory.service";
|
||||
import { SingleRequestBuilder } from "./single-request-builder";
|
||||
import { StateService } from "./state.service";
|
||||
import { StateService } from "./state-service/state.service";
|
||||
import { SyncService } from "./sync.service";
|
||||
import * as constants from "./sync.service";
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import { BatchRequestBuilder } from "./batch-request-builder";
|
||||
import { LdapDirectoryService } from "./directory-services/ldap-directory.service";
|
||||
import { I18nService } from "./i18n.service";
|
||||
import { SingleRequestBuilder } from "./single-request-builder";
|
||||
import { StateService } from "./state.service";
|
||||
import { StateService } from "./state-service/state.service";
|
||||
import { SyncService } from "./sync.service";
|
||||
import * as constants from "./sync.service";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user