|
|
@@ -27,20 +27,38 @@ class LdapService {
|
|
|
|
|
|
password?: string; // Necessary when bind type is user bind
|
|
|
|
|
|
+ client: ldap.Client;
|
|
|
+
|
|
|
+ searchBase: string;
|
|
|
+
|
|
|
constructor(username?: string, password?: string) {
|
|
|
+ const serverUrl = configManager?.getConfig('crowi', 'security:passport-ldap:serverUrl');
|
|
|
+
|
|
|
this.username = username;
|
|
|
this.password = password;
|
|
|
+
|
|
|
+ // parse serverUrl
|
|
|
+ // see: https://regex101.com/r/0tuYBB/1
|
|
|
+ const match = serverUrl.match(/(ldaps?:\/\/[^/]+)\/(.*)?/);
|
|
|
+ if (match == null || match.length < 1) {
|
|
|
+ const urlInvalidMessage = 'serverUrl is invalid';
|
|
|
+ logger.error(urlInvalidMessage);
|
|
|
+ throw new Error(urlInvalidMessage);
|
|
|
+ }
|
|
|
+ const url = match[1];
|
|
|
+ this.searchBase = match[2] || '';
|
|
|
+
|
|
|
+ this.client = ldap.createClient({
|
|
|
+ url,
|
|
|
+ });
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Execute search on LDAP server and return result
|
|
|
- * @param {string} filter Search filter
|
|
|
- * @param {string} base Base DN to execute search on
|
|
|
- * @returns {SearchEntry[]} Search result. Default scope is set to 'sub'.
|
|
|
+ * Bind to LDAP server.
|
|
|
+ * This method is declared independently, so multiple operations can be requested to the LDAP server with a single bind.
|
|
|
*/
|
|
|
- search(filter?: string, base?: string, scope: 'sub' | 'base' | 'one' = 'sub'): Promise<SearchResultEntry[]> {
|
|
|
+ bind(): Promise<void> {
|
|
|
const isLdapEnabled = configManager?.getConfig('crowi', 'security:passport-ldap:isEnabled');
|
|
|
-
|
|
|
if (!isLdapEnabled) {
|
|
|
const notEnabledMessage = 'LDAP is not enabled';
|
|
|
logger.error(notEnabledMessage);
|
|
|
@@ -49,41 +67,44 @@ class LdapService {
|
|
|
|
|
|
// get configurations
|
|
|
const isUserBind = configManager?.getConfig('crowi', 'security:passport-ldap:isUserBind');
|
|
|
- const serverUrl = configManager?.getConfig('crowi', 'security:passport-ldap:serverUrl');
|
|
|
const bindDN = configManager?.getConfig('crowi', 'security:passport-ldap:bindDN');
|
|
|
const bindCredentials = configManager?.getConfig('crowi', 'security:passport-ldap:bindDNPassword');
|
|
|
|
|
|
- // parse serverUrl
|
|
|
- // see: https://regex101.com/r/0tuYBB/1
|
|
|
- const match = serverUrl.match(/(ldaps?:\/\/[^/]+)\/(.*)?/);
|
|
|
- if (match == null || match.length < 1) {
|
|
|
- const urlInvalidMessage = 'serverUrl is invalid';
|
|
|
- logger.error(urlInvalidMessage);
|
|
|
- throw new Error(urlInvalidMessage);
|
|
|
- }
|
|
|
- const url = match[1];
|
|
|
- const searchBase = match[2] || '';
|
|
|
-
|
|
|
// user bind
|
|
|
const fixedBindDN = (isUserBind)
|
|
|
? bindDN.replace(/{{username}}/, this.username)
|
|
|
: bindDN;
|
|
|
const fixedBindCredentials = (isUserBind) ? this.password : bindCredentials;
|
|
|
|
|
|
- const client = ldap.createClient({
|
|
|
- url,
|
|
|
+ return new Promise<void>((resolve, reject) => {
|
|
|
+ this.client.bind(fixedBindDN, fixedBindCredentials, (err) => {
|
|
|
+ if (err != null) {
|
|
|
+ reject(err);
|
|
|
+ }
|
|
|
+ resolve();
|
|
|
+ });
|
|
|
});
|
|
|
+ }
|
|
|
|
|
|
+ /**
|
|
|
+ * Execute search on LDAP server and return result
|
|
|
+ * Execution of bind() is necessary before search
|
|
|
+ * @param {string} filter Search filter
|
|
|
+ * @param {string} base Base DN to execute search on
|
|
|
+ * @returns {SearchEntry[]} Search result. Default scope is set to 'sub'.
|
|
|
+ */
|
|
|
+ search(filter?: string, base?: string, scope: 'sub' | 'base' | 'one' = 'sub'): Promise<SearchResultEntry[]> {
|
|
|
const searchResults: SearchResultEntry[] = [];
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
- client.bind(fixedBindDN, fixedBindCredentials, (err) => {
|
|
|
- if (err != null) {
|
|
|
- reject(err);
|
|
|
- }
|
|
|
+ // reject on client connection error (occures when not binded or host is not found)
|
|
|
+ this.client.on('error', (err) => {
|
|
|
+ reject(err);
|
|
|
});
|
|
|
|
|
|
- client.search(base || searchBase, { scope, filter }, (err, res) => {
|
|
|
+ this.client.search(base || this.searchBase, {
|
|
|
+ scope, filter, paged: true, sizeLimit: 200,
|
|
|
+ }, (err, res) => {
|
|
|
if (err != null) {
|
|
|
reject(err);
|
|
|
}
|