fix(ldap): allowing usage of searchStreaming function in no-paged

Signed-off-by: Antonio Musolino <antoniomusolino007@gmail.com>
This commit is contained in:
Antonio Musolino
2022-08-08 16:12:19 +02:00
parent 5097e54087
commit 3f0db06a79
3 changed files with 114 additions and 46 deletions
@@ -14,12 +14,12 @@
* limitations under the License.
*/
import { ForwardedError } from '@backstage/errors';
import { ForwardedError, stringifyError } from '@backstage/errors';
import ldap, { Client, SearchEntry, SearchOptions } from 'ldapjs';
import { cloneDeep, merge } from 'lodash';
import { cloneDeep } from 'lodash';
import { Logger } from 'winston';
import { BindConfig, TLSConfig } from './config';
import { errorString } from './util';
import { createOptions, errorString } from './util';
import {
ActiveDirectoryVendor,
DefaultLdapVendor,
@@ -150,54 +150,60 @@ export class LdapClient {
f: (entry: SearchEntry) => Promise<void> | void,
): Promise<void> {
try {
let awaitList: Array<Promise<void> | void> = [];
return await new Promise<void>((resolve, reject) => {
// Note that we clone the (frozen) options, since ldapjs rudely tries to
// overwrite parts of them
this.client.search(
dn,
merge(cloneDeep(options), { paged: { pagePause: true } }),
(err, res) => {
if (err) {
reject(new Error(errorString(err)));
}
this.client.search(dn, createOptions(options), (err, res) => {
if (err) {
reject(new Error(errorString(err)));
}
let awaitList: Array<Promise<void> | void> = [];
let transformError = false;
res.on('searchReference', () => {
this.logger.warn('Received unsupported search referral');
});
const transformReject = (e: Error) => {
transformError = true;
reject(
new Error(
`Transform function threw an exception, ${stringifyError(e)}`,
),
);
};
res.on('searchEntry', entry => {
awaitList.push(f(entry));
});
res.on('searchReference', () => {
this.logger.warn('Received unsupported search referral');
});
res.on('page', (_, cb) => {
// awaits completion before fetching next page
res.on('searchEntry', entry => {
if (!transformError) awaitList.push(f(entry));
});
res.on('page', (_, cb) => {
// awaits completion before fetching next page
Promise.all(awaitList)
.then(() => {
// flush list
awaitList = [];
if (cb) cb();
})
.catch(transformReject);
});
res.on('error', e => {
reject(new Error(errorString(e)));
});
res.on('end', r => {
if (!r) {
throw new Error('Null response');
} else if (r.status !== 0) {
throw new Error(`Got status ${r.status}: ${r.errorMessage}`);
} else {
Promise.all(awaitList)
.then(() => {
// flush list
awaitList = [];
if (cb) cb();
})
.catch(reject);
});
res.on('error', e => {
reject(new Error(errorString(e)));
});
res.on('end', r => {
if (!r) {
throw new Error('Null response');
} else if (r.status !== 0) {
throw new Error(`Got status ${r.status}: ${r.errorMessage}`);
} else {
Promise.all(awaitList)
.then(() => resolve())
.catch(reject);
}
});
},
);
.then(() => resolve())
.catch(transformReject);
}
});
});
});
} catch (e) {
throw new ForwardedError(`LDAP search at DN "${dn}" failed`, e);
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { errorString } from './util';
import { errorString, createOptions } from './util';
describe('errorString', () => {
it('formats', () => {
@@ -22,3 +22,52 @@ describe('errorString', () => {
expect(errorString(e)).toEqual('1 n: m');
});
});
describe('createOptions', () => {
it('should add pagePause', () => {
const options = {
filter: 'f',
paged: true,
timeLimit: 42,
sizeLimit: 100,
derefAliases: 0,
typesOnly: false,
};
expect(createOptions(options)).toEqual({
filter: 'f',
paged: { pagePause: true },
timeLimit: 42,
sizeLimit: 100,
derefAliases: 0,
typesOnly: false,
});
const options2 = {
filter: 'f',
paged: {},
timeLimit: 42,
sizeLimit: 100,
derefAliases: 0,
typesOnly: false,
};
expect(createOptions(options2)).toEqual({
filter: 'f',
paged: { pagePause: true },
timeLimit: 42,
sizeLimit: 100,
derefAliases: 0,
typesOnly: false,
});
});
it('should not add pagePause', () => {
const options = {
filter: 'f',
paged: false,
timeLimit: 42,
sizeLimit: 100,
derefAliases: 0,
typesOnly: false,
};
expect(createOptions(options)).toEqual(options);
});
});
@@ -14,7 +14,8 @@
* limitations under the License.
*/
import { Error as LDAPError, SearchEntry } from 'ldapjs';
import { Error as LDAPError, SearchEntry, SearchOptions } from 'ldapjs';
import { cloneDeep } from 'lodash';
import { LdapVendor } from './vendors';
/**
@@ -54,6 +55,18 @@ export function mapStringAttr(
}
}
export function createOptions(inputOptions: SearchOptions): SearchOptions {
const result = cloneDeep(inputOptions);
if (result.paged === true) {
result.paged = { pagePause: true };
} else if (typeof result.paged === 'object') {
result.paged.pagePause = true;
}
return result;
}
export type RecursivePartial<T> = {
[P in keyof T]?: T[P] extends (infer U)[]
? RecursivePartial<U>[]