Files
Automata/packages/cli/src/Ldap/LdapSync.ee.ts
कारतोफ्फेलस्क्रिप्ट™ 52f740b9e8 refactor(core): Use an IoC container to manage singleton classes [Part-1] (no-changelog) (#5509)
* add typedi

* convert ActiveWorkflowRunner into an injectable service

* convert ExternalHooks into an injectable service

* convert InternalHooks into an injectable service

* convert LoadNodesAndCredentials into an injectable service

* convert NodeTypes and CredentialTypes into an injectable service

* convert ActiveExecutions into an injectable service

* convert WaitTracker into an injectable service

* convert Push into an injectable service

* convert ActiveWebhooks and  TestWebhooks into an injectable services

* handle circular references, and log errors when a circular dependency is found
2023-02-21 19:21:56 +01:00

218 lines
5.9 KiB
TypeScript

import type { Entry as LdapUser } from 'ldapts';
import { LoggerProxy as Logger } from 'n8n-workflow';
import { QueryFailedError } from 'typeorm/error/QueryFailedError';
import type { LdapService } from './LdapService.ee';
import type { LdapConfig } from './types';
import {
getLdapUserRole,
mapLdapUserToDbUser,
processUsers,
saveLdapSynchronization,
createFilter,
resolveBinaryAttributes,
getLdapIds,
} from './helpers';
import type { User } from '@db/entities/User';
import type { Role } from '@db/entities/Role';
import type { RunningMode, SyncStatus } from '@db/entities/AuthProviderSyncHistory';
import { Container } from 'typedi';
import { InternalHooks } from '@/InternalHooks';
export class LdapSync {
private intervalId: NodeJS.Timeout | undefined = undefined;
private _config: LdapConfig;
private _ldapService: LdapService;
/**
* Updates the LDAP configuration
*/
set config(config: LdapConfig) {
this._config = config;
// If user disabled synchronization in the UI and there a job schedule,
// stop it
if (this.intervalId && !this._config.synchronizationEnabled) {
this.stop();
// If instance crashed with a job scheduled, once the server starts
// again, reschedule it.
} else if (!this.intervalId && this._config.synchronizationEnabled) {
this.scheduleRun();
// If job scheduled and the run interval got updated in the UI
// stop the current one and schedule a new one with the new internal
} else if (this.intervalId && this._config.synchronizationEnabled) {
this.stop();
this.scheduleRun();
}
}
/**
* Set the LDAP service instance
*/
set ldapService(service: LdapService) {
this._ldapService = service;
}
/**
* Schedule a synchronization job based on the interval set in the LDAP config
*/
scheduleRun(): void {
if (!this._config.synchronizationInterval) {
throw new Error('Interval variable has to be defined');
}
this.intervalId = setInterval(async () => {
await this.run('live');
}, this._config.synchronizationInterval * 60000);
}
/**
* Run the synchronization job.
* If the job runs in "live" mode, changes to LDAP users are persisted in the database,
* else the users are not modified
*/
async run(mode: RunningMode): Promise<void> {
Logger.debug(`LDAP - Starting a synchronization run in ${mode} mode`);
let adUsers: LdapUser[] = [];
try {
adUsers = await this._ldapService.searchWithAdminBinding(
createFilter(`(${this._config.loginIdAttribute}=*)`, this._config.userFilter),
);
Logger.debug('LDAP - Users return by the query', {
users: adUsers,
});
resolveBinaryAttributes(adUsers);
} catch (e) {
if (e instanceof Error) {
Logger.error(`LDAP - ${e.message}`);
throw e;
}
}
const startedAt = new Date();
const localAdUsers = await getLdapIds();
const role = await getLdapUserRole();
const { usersToCreate, usersToUpdate, usersToDisable } = this.getUsersToProcess(
adUsers,
localAdUsers,
role,
);
if (usersToDisable.length) {
void Container.get(InternalHooks).onLdapUsersDisabled({
reason: 'ldap_update',
users: usersToDisable.length,
user_ids: usersToDisable,
});
}
Logger.debug('LDAP - Users processed', {
created: usersToCreate.length,
updated: usersToUpdate.length,
disabled: usersToDisable.length,
});
const endedAt = new Date();
let status: SyncStatus = 'success';
let errorMessage = '';
try {
if (mode === 'live') {
await processUsers(usersToCreate, usersToUpdate, usersToDisable);
}
} catch (error) {
if (error instanceof QueryFailedError) {
status = 'error';
errorMessage = `${error.message}`;
}
}
await saveLdapSynchronization({
startedAt,
endedAt,
created: usersToCreate.length,
updated: usersToUpdate.length,
disabled: usersToDisable.length,
scanned: adUsers.length,
runMode: mode,
status,
error: errorMessage,
});
void Container.get(InternalHooks).onLdapSyncFinished({
type: !this.intervalId ? 'scheduled' : `manual_${mode}`,
succeeded: true,
users_synced: usersToCreate.length + usersToUpdate.length + usersToDisable.length,
error: errorMessage,
});
Logger.debug('LDAP - Synchronization finished successfully');
}
/**
* Stop the current job scheduled, if any
*/
stop(): void {
clearInterval(this.intervalId);
this.intervalId = undefined;
}
/**
* Get all the user that will be changed (created, updated, disabled), in the database
*/
private getUsersToProcess(
adUsers: LdapUser[],
localAdUsers: string[],
role: Role,
): {
usersToCreate: Array<[string, User]>;
usersToUpdate: Array<[string, User]>;
usersToDisable: string[];
} {
return {
usersToCreate: this.getUsersToCreate(adUsers, localAdUsers, role),
usersToUpdate: this.getUsersToUpdate(adUsers, localAdUsers),
usersToDisable: this.getUsersToDisable(adUsers, localAdUsers),
};
}
/**
* Get users in LDAP that are not in the database yet
*/
private getUsersToCreate(
remoteAdUsers: LdapUser[],
localLdapIds: string[],
role: Role,
): Array<[string, User]> {
return remoteAdUsers
.filter((adUser) => !localLdapIds.includes(adUser[this._config.ldapIdAttribute] as string))
.map((adUser) => mapLdapUserToDbUser(adUser, this._config, role));
}
/**
* Get users in LDAP that are already in the database
*/
private getUsersToUpdate(
remoteAdUsers: LdapUser[],
localLdapIds: string[],
): Array<[string, User]> {
return remoteAdUsers
.filter((adUser) => localLdapIds.includes(adUser[this._config.ldapIdAttribute] as string))
.map((adUser) => mapLdapUserToDbUser(adUser, this._config));
}
/**
* Get users that are in the database but not in the LDAP server
*/
private getUsersToDisable(remoteAdUsers: LdapUser[], localLdapIds: string[]): string[] {
const remoteAdUserIds = remoteAdUsers.map((adUser) => adUser[this._config.ldapIdAttribute]);
return localLdapIds.filter((user) => !remoteAdUserIds.includes(user));
}
}