mirror of
https://github.com/wekan/wekan.git
synced 2026-02-14 20:18:07 +01:00
Merge pull request #6115 from harryadel/wekan-ldap-async-migration
Some checks are pending
Some checks are pending
Migrate wekan-ldap to async API for Meteor 3.0
This commit is contained in:
commit
5a5eaec340
7 changed files with 218 additions and 348 deletions
|
|
@ -143,8 +143,7 @@ wekan-accounts-oidc@1.0.10
|
|||
wekan-accounts-sandstorm@0.9.0
|
||||
wekan-fontawesome@6.4.2
|
||||
wekan-fullcalendar@3.10.5
|
||||
wekan-ldap@0.0.2
|
||||
wekan-ldap@0.1.0
|
||||
wekan-markdown@1.0.9
|
||||
wekan-oidc@1.1.0
|
||||
yasaricli:slugify@0.0.7
|
||||
zodern:types@1.0.13
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Package.describe({
|
||||
name: 'wekan-ldap',
|
||||
version: '0.0.2',
|
||||
version: '0.1.0',
|
||||
// Brief, one-line summary of the package.
|
||||
summary: 'Basic meteor login with ldap',
|
||||
// URL to the Git repository containing the source code for this package.
|
||||
|
|
@ -12,7 +12,6 @@ Package.describe({
|
|||
|
||||
|
||||
Package.onUse(function(api) {
|
||||
api.use('yasaricli:slugify');
|
||||
api.use('ecmascript');
|
||||
api.use('underscore');
|
||||
api.use('sha');
|
||||
|
|
@ -25,3 +24,8 @@ Package.onUse(function(api) {
|
|||
|
||||
api.mainModule('server/index.js', 'server');
|
||||
});
|
||||
|
||||
Npm.depends({
|
||||
'ldapts': '4.2.6',
|
||||
'limax': '4.1.0'
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import ldapjs from 'ldapjs';
|
||||
import { Client } from 'ldapts';
|
||||
import { Log } from 'meteor/logging';
|
||||
|
||||
// copied from https://github.com/ldapjs/node-ldapjs/blob/a113953e0d91211eb945d2a3952c84b7af6de41c/lib/filters/index.js#L167
|
||||
|
|
@ -18,10 +18,14 @@ function escapedToHex (str) {
|
|||
}
|
||||
}
|
||||
|
||||
// Convert hex string to LDAP escaped binary filter value
|
||||
// e.g. "0102ff" -> "\\01\\02\\ff"
|
||||
function hexToLdapEscaped(hex) {
|
||||
return hex.match(/.{2}/g).map(h => '\\' + h).join('');
|
||||
}
|
||||
|
||||
export default class LDAP {
|
||||
constructor() {
|
||||
this.ldapjs = ldapjs;
|
||||
|
||||
this.connected = false;
|
||||
|
||||
this.options = {
|
||||
|
|
@ -73,34 +77,9 @@ export default class LDAP {
|
|||
}
|
||||
}
|
||||
|
||||
connectSync(...args) {
|
||||
if (!this._connectSync) {
|
||||
this._connectSync = Meteor.wrapAsync(this.connectAsync, this);
|
||||
}
|
||||
return this._connectSync(...args);
|
||||
}
|
||||
|
||||
searchAllSync(...args) {
|
||||
|
||||
if (!this._searchAllSync) {
|
||||
this._searchAllSync = Meteor.wrapAsync(this.searchAllAsync, this);
|
||||
}
|
||||
return this._searchAllSync(...args);
|
||||
}
|
||||
|
||||
connectAsync(callback) {
|
||||
async connect() {
|
||||
Log.info('Init setup');
|
||||
|
||||
let replied = false;
|
||||
|
||||
const connectionOptions = {
|
||||
url : `${this.options.host}:${this.options.port}`,
|
||||
timeout : this.options.timeout,
|
||||
connectTimeout: this.options.connect_timeout,
|
||||
idleTimeout : this.options.idle_timeout,
|
||||
reconnect : this.options.Reconnect,
|
||||
};
|
||||
|
||||
const tlsOptions = {
|
||||
rejectUnauthorized: this.options.reject_unauthorized,
|
||||
};
|
||||
|
|
@ -120,81 +99,114 @@ export default class LDAP {
|
|||
tlsOptions.ca = ca;
|
||||
}
|
||||
|
||||
let url;
|
||||
if (this.options.encryption === 'ssl') {
|
||||
connectionOptions.url = `ldaps://${connectionOptions.url}`;
|
||||
connectionOptions.tlsOptions = tlsOptions;
|
||||
url = `ldaps://${this.options.host}:${this.options.port}`;
|
||||
} else {
|
||||
connectionOptions.url = `ldap://${connectionOptions.url}`;
|
||||
url = `ldap://${this.options.host}:${this.options.port}`;
|
||||
}
|
||||
|
||||
Log.info(`Connecting ${connectionOptions.url}`);
|
||||
Log.debug(`connectionOptions ${JSON.stringify(connectionOptions)}`);
|
||||
Log.info(`Connecting ${url}`);
|
||||
|
||||
this.client = ldapjs.createClient(connectionOptions);
|
||||
const clientOptions = {
|
||||
url,
|
||||
timeout : this.options.timeout,
|
||||
connectTimeout: this.options.connect_timeout,
|
||||
strictDN : false,
|
||||
};
|
||||
|
||||
this.bindSync = Meteor.wrapAsync(this.client.bind, this.client);
|
||||
if (this.options.encryption === 'ssl') {
|
||||
clientOptions.tlsOptions = tlsOptions;
|
||||
}
|
||||
|
||||
this.client.on('error', (error) => {
|
||||
Log.error(`connection ${error}`);
|
||||
if (replied === false) {
|
||||
replied = true;
|
||||
callback(error, null);
|
||||
}
|
||||
});
|
||||
Log.debug(`clientOptions ${JSON.stringify(clientOptions)}`);
|
||||
|
||||
this.client.on('idle', () => {
|
||||
Log.info('Idle');
|
||||
this.disconnect();
|
||||
});
|
||||
|
||||
this.client.on('close', () => {
|
||||
Log.info('Closed');
|
||||
});
|
||||
this.client = new Client(clientOptions);
|
||||
|
||||
if (this.options.encryption === 'tls') {
|
||||
// Set host parameter for tls.connect which is used by ldapjs starttls. This shouldn't be needed in newer nodejs versions (e.g v5.6.0).
|
||||
// Set host parameter for tls.connect which is used by starttls. This shouldn't be needed in newer nodejs versions (e.g v5.6.0).
|
||||
// https://github.com/RocketChat/Rocket.Chat/issues/2035
|
||||
// https://github.com/mcavage/node-ldapjs/issues/349
|
||||
tlsOptions.host = this.options.host;
|
||||
|
||||
Log.info('Starting TLS');
|
||||
Log.debug(`tlsOptions ${JSON.stringify(tlsOptions)}`);
|
||||
|
||||
this.client.starttls(tlsOptions, null, (error, response) => {
|
||||
if (error) {
|
||||
Log.error(`TLS connection ${JSON.stringify(error)}`);
|
||||
if (replied === false) {
|
||||
replied = true;
|
||||
callback(error, null);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Log.info('TLS connected');
|
||||
this.connected = true;
|
||||
if (replied === false) {
|
||||
replied = true;
|
||||
callback(null, response);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.client.on('connect', (response) => {
|
||||
Log.info('LDAP connected');
|
||||
this.connected = true;
|
||||
if (replied === false) {
|
||||
replied = true;
|
||||
callback(null, response);
|
||||
}
|
||||
});
|
||||
await this.client.startTLS(tlsOptions);
|
||||
Log.info('TLS connected');
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
if (replied === false) {
|
||||
Log.error(`connection time out ${connectionOptions.connectTimeout}`);
|
||||
replied = true;
|
||||
callback(new Error('Timeout'));
|
||||
this.connected = true;
|
||||
}
|
||||
|
||||
async bind(dn, password) {
|
||||
await this.client.bind(dn, password);
|
||||
}
|
||||
|
||||
getBufferAttributes() {
|
||||
const fields = [];
|
||||
let uidField = this.constructor.settings_get('LDAP_UNIQUE_IDENTIFIER_FIELD');
|
||||
if (uidField && uidField !== '') {
|
||||
fields.push(...uidField.replace(/\s/g, '').split(','));
|
||||
}
|
||||
let searchField = this.constructor.settings_get('LDAP_USER_SEARCH_FIELD');
|
||||
if (searchField && searchField !== '') {
|
||||
fields.push(...searchField.replace(/\s/g, '').split(','));
|
||||
}
|
||||
return fields;
|
||||
}
|
||||
|
||||
async searchAll(BaseDN, options) {
|
||||
const searchOptions = {
|
||||
filter: options.filter,
|
||||
scope : options.scope || 'sub',
|
||||
};
|
||||
|
||||
if (options.attributes) {
|
||||
searchOptions.attributes = options.attributes;
|
||||
}
|
||||
|
||||
if (options.sizeLimit) {
|
||||
searchOptions.sizeLimit = options.sizeLimit;
|
||||
}
|
||||
|
||||
if (options.paged) {
|
||||
searchOptions.paged = {
|
||||
pageSize: options.paged.pageSize || 250,
|
||||
};
|
||||
}
|
||||
|
||||
// Request unique identifier fields as Buffers so that
|
||||
// getLdapUserUniqueID() in sync.js can call .toString('hex')
|
||||
const bufferAttributes = this.getBufferAttributes();
|
||||
if (bufferAttributes.length > 0) {
|
||||
searchOptions.explicitBufferAttributes = bufferAttributes;
|
||||
}
|
||||
|
||||
const { searchEntries } = await this.client.search(BaseDN, searchOptions);
|
||||
|
||||
Log.info(`Search result count ${searchEntries.length}`);
|
||||
return searchEntries.map((entry) => this.extractLdapEntryData(entry));
|
||||
}
|
||||
|
||||
extractLdapEntryData(entry) {
|
||||
const values = {
|
||||
_raw: {},
|
||||
};
|
||||
|
||||
for (const key of Object.keys(entry)) {
|
||||
const value = entry[key];
|
||||
values._raw[key] = value;
|
||||
|
||||
if (!['thumbnailPhoto', 'jpegPhoto'].includes(key)) {
|
||||
if (value instanceof Buffer) {
|
||||
values[key] = value.toString();
|
||||
} else {
|
||||
values[key] = value;
|
||||
}
|
||||
}
|
||||
}, connectionOptions.connectTimeout);
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
getUserFilter(username) {
|
||||
|
|
@ -223,7 +235,7 @@ export default class LDAP {
|
|||
return `(&${filter.join('')})`;
|
||||
}
|
||||
|
||||
bindUserIfNecessary(username, password) {
|
||||
async bindUserIfNecessary(username, password) {
|
||||
|
||||
if (this.domainBinded === true) {
|
||||
return;
|
||||
|
|
@ -247,11 +259,11 @@ export default class LDAP {
|
|||
|
||||
Log.info(`Binding with User ${userDn}`);
|
||||
|
||||
this.bindSync(userDn, password);
|
||||
await this.bind(userDn, password);
|
||||
this.domainBinded = true;
|
||||
}
|
||||
|
||||
bindIfNecessary() {
|
||||
async bindIfNecessary() {
|
||||
if (this.domainBinded === true) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -262,12 +274,12 @@ export default class LDAP {
|
|||
|
||||
Log.info(`Binding UserDN ${this.options.Authentication_UserDN}`);
|
||||
|
||||
this.bindSync(this.options.Authentication_UserDN, this.options.Authentication_Password);
|
||||
await this.bind(this.options.Authentication_UserDN, this.options.Authentication_Password);
|
||||
this.domainBinded = true;
|
||||
}
|
||||
|
||||
searchUsersSync(username, page) {
|
||||
this.bindIfNecessary();
|
||||
async searchUsers(username) {
|
||||
await this.bindIfNecessary();
|
||||
const searchOptions = {
|
||||
filter : this.getUserFilter(username),
|
||||
scope : this.options.User_Search_Scope || 'sub',
|
||||
|
|
@ -278,8 +290,7 @@ export default class LDAP {
|
|||
|
||||
if (this.options.Search_Page_Size > 0) {
|
||||
searchOptions.paged = {
|
||||
pageSize : this.options.Search_Page_Size,
|
||||
pagePause: !!page,
|
||||
pageSize: this.options.Search_Page_Size,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -287,35 +298,22 @@ export default class LDAP {
|
|||
Log.debug(`searchOptions ${searchOptions}`);
|
||||
Log.debug(`BaseDN ${this.options.BaseDN}`);
|
||||
|
||||
if (page) {
|
||||
return this.searchAllPaged(this.options.BaseDN, searchOptions, page);
|
||||
}
|
||||
|
||||
return this.searchAllSync(this.options.BaseDN, searchOptions);
|
||||
return await this.searchAll(this.options.BaseDN, searchOptions);
|
||||
}
|
||||
|
||||
getUserByIdSync(id, attribute) {
|
||||
this.bindIfNecessary();
|
||||
async getUserById(id, attribute) {
|
||||
await this.bindIfNecessary();
|
||||
|
||||
const Unique_Identifier_Field = this.constructor.settings_get('LDAP_UNIQUE_IDENTIFIER_FIELD').split(',');
|
||||
|
||||
const escapedValue = hexToLdapEscaped(id);
|
||||
let filter;
|
||||
|
||||
if (attribute) {
|
||||
filter = new this.ldapjs.filters.EqualityFilter({
|
||||
attribute,
|
||||
value: Buffer.from(id, 'hex'),
|
||||
});
|
||||
filter = `(${attribute}=${escapedValue})`;
|
||||
} else {
|
||||
const filters = [];
|
||||
Unique_Identifier_Field.forEach((item) => {
|
||||
filters.push(new this.ldapjs.filters.EqualityFilter({
|
||||
attribute: item,
|
||||
value : Buffer.from(id, 'hex'),
|
||||
}));
|
||||
});
|
||||
|
||||
filter = new this.ldapjs.filters.OrFilter({ filters });
|
||||
const filters = Unique_Identifier_Field.map((item) => `(${item}=${escapedValue})`);
|
||||
filter = `(|${filters.join('')})`;
|
||||
}
|
||||
|
||||
const searchOptions = {
|
||||
|
|
@ -324,10 +322,10 @@ export default class LDAP {
|
|||
};
|
||||
|
||||
Log.info(`Searching by id ${id}`);
|
||||
Log.debug(`search filter ${searchOptions.filter.toString()}`);
|
||||
Log.debug(`search filter ${searchOptions.filter}`);
|
||||
Log.debug(`BaseDN ${this.options.BaseDN}`);
|
||||
|
||||
const result = this.searchAllSync(this.options.BaseDN, searchOptions);
|
||||
const result = await this.searchAll(this.options.BaseDN, searchOptions);
|
||||
|
||||
if (!Array.isArray(result) || result.length === 0) {
|
||||
return;
|
||||
|
|
@ -340,8 +338,8 @@ export default class LDAP {
|
|||
return result[0];
|
||||
}
|
||||
|
||||
getUserByUsernameSync(username) {
|
||||
this.bindIfNecessary();
|
||||
async getUserByUsername(username) {
|
||||
await this.bindIfNecessary();
|
||||
|
||||
const searchOptions = {
|
||||
filter: this.getUserFilter(username),
|
||||
|
|
@ -352,7 +350,7 @@ export default class LDAP {
|
|||
Log.debug(`searchOptions ${searchOptions}`);
|
||||
Log.debug(`BaseDN ${this.options.BaseDN}`);
|
||||
|
||||
const result = this.searchAllSync(this.options.BaseDN, searchOptions);
|
||||
const result = await this.searchAll(this.options.BaseDN, searchOptions);
|
||||
|
||||
if (!Array.isArray(result) || result.length === 0) {
|
||||
return;
|
||||
|
|
@ -365,7 +363,7 @@ export default class LDAP {
|
|||
return result[0];
|
||||
}
|
||||
|
||||
getUserGroups(username, ldapUser) {
|
||||
async getUserGroups(username, ldapUser) {
|
||||
if (!this.options.group_filter_enabled) {
|
||||
return true;
|
||||
}
|
||||
|
|
@ -394,7 +392,7 @@ export default class LDAP {
|
|||
|
||||
Log.debug(`Group list filter LDAP: ${searchOptions.filter}`);
|
||||
|
||||
const result = this.searchAllSync(this.options.BaseDN, searchOptions);
|
||||
const result = await this.searchAll(this.options.BaseDN, searchOptions);
|
||||
|
||||
if (!Array.isArray(result) || result.length === 0) {
|
||||
return [];
|
||||
|
|
@ -410,12 +408,12 @@ export default class LDAP {
|
|||
|
||||
}
|
||||
|
||||
isUserInGroup(username, ldapUser) {
|
||||
async isUserInGroup(username, ldapUser) {
|
||||
if (!this.options.group_filter_enabled) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const grps = this.getUserGroups(username, ldapUser);
|
||||
const grps = await this.getUserGroups(username, ldapUser);
|
||||
|
||||
const filter = ['(&'];
|
||||
|
||||
|
|
@ -444,7 +442,7 @@ export default class LDAP {
|
|||
|
||||
Log.debug(`Group filter LDAP: ${searchOptions.filter}`);
|
||||
|
||||
const result = this.searchAllSync(this.options.BaseDN, searchOptions);
|
||||
const result = await this.searchAll(this.options.BaseDN, searchOptions);
|
||||
|
||||
if (!Array.isArray(result) || result.length === 0) {
|
||||
return false;
|
||||
|
|
@ -452,142 +450,14 @@ export default class LDAP {
|
|||
return true;
|
||||
}
|
||||
|
||||
extractLdapEntryData(entry) {
|
||||
const values = {
|
||||
_raw: entry.raw,
|
||||
};
|
||||
|
||||
Object.keys(values._raw).forEach((key) => {
|
||||
const value = values._raw[key];
|
||||
|
||||
if (!['thumbnailPhoto', 'jpegPhoto'].includes(key)) {
|
||||
if (value instanceof Buffer) {
|
||||
values[key] = value.toString();
|
||||
} else {
|
||||
values[key] = value;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
searchAllPaged(BaseDN, options, page) {
|
||||
this.bindIfNecessary();
|
||||
|
||||
const processPage = ({ entries, title, end, next }) => {
|
||||
Log.info(title);
|
||||
// Force LDAP idle to wait the record processing
|
||||
this.client._updateIdle(true);
|
||||
page(null, entries, {
|
||||
end, next: () => {
|
||||
// Reset idle timer
|
||||
this.client._updateIdle();
|
||||
next && next();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
this.client.search(BaseDN, options, (error, res) => {
|
||||
if (error) {
|
||||
Log.error(error);
|
||||
page(error);
|
||||
return;
|
||||
}
|
||||
|
||||
res.on('error', (error) => {
|
||||
Log.error(error);
|
||||
page(error);
|
||||
return;
|
||||
});
|
||||
|
||||
let entries = [];
|
||||
|
||||
const internalPageSize = options.paged && options.paged.pageSize > 0 ? options.paged.pageSize * 2 : 500;
|
||||
|
||||
res.on('searchEntry', (entry) => {
|
||||
entries.push(this.extractLdapEntryData(entry));
|
||||
|
||||
if (entries.length >= internalPageSize) {
|
||||
processPage({
|
||||
entries,
|
||||
title: 'Internal Page',
|
||||
end : false,
|
||||
});
|
||||
entries = [];
|
||||
}
|
||||
});
|
||||
|
||||
res.on('page', (result, next) => {
|
||||
if (!next) {
|
||||
this.client._updateIdle(true);
|
||||
processPage({
|
||||
entries,
|
||||
title: 'Final Page',
|
||||
end : true,
|
||||
});
|
||||
} else if (entries.length) {
|
||||
Log.info('Page');
|
||||
processPage({
|
||||
entries,
|
||||
title: 'Page',
|
||||
end : false,
|
||||
next,
|
||||
});
|
||||
entries = [];
|
||||
}
|
||||
});
|
||||
|
||||
res.on('end', () => {
|
||||
if (entries.length) {
|
||||
processPage({
|
||||
entries,
|
||||
title: 'Final Page',
|
||||
end : true,
|
||||
});
|
||||
entries = [];
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
searchAllAsync(BaseDN, options, callback) {
|
||||
this.bindIfNecessary();
|
||||
|
||||
this.client.search(BaseDN, options, (error, res) => {
|
||||
if (error) {
|
||||
Log.error(error);
|
||||
callback(error);
|
||||
return;
|
||||
}
|
||||
|
||||
res.on('error', (error) => {
|
||||
Log.error(error);
|
||||
callback(error);
|
||||
return;
|
||||
});
|
||||
|
||||
const entries = [];
|
||||
|
||||
res.on('searchEntry', (entry) => {
|
||||
entries.push(this.extractLdapEntryData(entry));
|
||||
});
|
||||
|
||||
res.on('end', () => {
|
||||
Log.info(`Search result count ${entries.length}`);
|
||||
callback(null, entries);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
authSync(dn, password) {
|
||||
async auth(dn, password) {
|
||||
Log.info(`Authenticating ${dn}`);
|
||||
|
||||
try {
|
||||
if (password === '') {
|
||||
throw new Error('Password is not provided');
|
||||
}
|
||||
this.bindSync(dn, password);
|
||||
await this.bind(dn, password);
|
||||
Log.info(`Authenticated ${dn}`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
|
|
@ -597,10 +467,14 @@ export default class LDAP {
|
|||
}
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
async disconnect() {
|
||||
this.connected = false;
|
||||
this.domainBinded = false;
|
||||
Log.info('Disconecting');
|
||||
this.client.unbind();
|
||||
try {
|
||||
await this.client.unbind();
|
||||
} catch (error) {
|
||||
Log.debug('Error during disconnect', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ function fallbackDefaultAccountSystem(bind, username, password) {
|
|||
return Accounts._runLoginHandlers(bind, loginRequest);
|
||||
}
|
||||
|
||||
Accounts.registerLoginHandler('ldap', function(loginRequest) {
|
||||
Accounts.registerLoginHandler('ldap', async function(loginRequest) {
|
||||
if (!loginRequest.ldap || !loginRequest.ldapOptions) {
|
||||
return undefined;
|
||||
}
|
||||
|
|
@ -42,27 +42,27 @@ Accounts.registerLoginHandler('ldap', function(loginRequest) {
|
|||
|
||||
try {
|
||||
|
||||
ldap.connectSync();
|
||||
await ldap.connect();
|
||||
|
||||
if (!!LDAP.settings_get('LDAP_USER_AUTHENTICATION')) {
|
||||
ldap.bindUserIfNecessary(loginRequest.username, loginRequest.ldapPass);
|
||||
ldapUser = ldap.searchUsersSync(loginRequest.username)[0];
|
||||
await ldap.bindUserIfNecessary(loginRequest.username, loginRequest.ldapPass);
|
||||
ldapUser = (await ldap.searchUsers(loginRequest.username))[0];
|
||||
} else {
|
||||
|
||||
const users = ldap.searchUsersSync(loginRequest.username);
|
||||
const users = await ldap.searchUsers(loginRequest.username);
|
||||
|
||||
if (users.length !== 1) {
|
||||
log_info('Search returned', users.length, 'record(s) for', loginRequest.username);
|
||||
throw new Error('User not Found');
|
||||
}
|
||||
|
||||
if (ldap.isUserInGroup(loginRequest.username, users[0])) {
|
||||
if (await ldap.isUserInGroup(loginRequest.username, users[0])) {
|
||||
ldapUser = users[0];
|
||||
} else {
|
||||
throw new Error('User not in a valid group');
|
||||
}
|
||||
|
||||
if (ldap.authSync(users[0].dn, loginRequest.ldapPass) !== true) {
|
||||
if (await ldap.auth(users[0].dn, loginRequest.ldapPass) !== true) {
|
||||
ldapUser = null;
|
||||
log_info('Wrong password for', loginRequest.username)
|
||||
}
|
||||
|
|
@ -96,7 +96,7 @@ Accounts.registerLoginHandler('ldap', function(loginRequest) {
|
|||
log_info('Querying user');
|
||||
log_debug('userQuery', userQuery);
|
||||
|
||||
user = Meteor.users.findOne(userQuery);
|
||||
user = await Meteor.users.findOneAsync(userQuery);
|
||||
}
|
||||
|
||||
// Attempt to find user by username
|
||||
|
|
@ -137,7 +137,7 @@ Accounts.registerLoginHandler('ldap', function(loginRequest) {
|
|||
|
||||
log_debug('userQuery', userQuery);
|
||||
|
||||
user = Meteor.users.findOne(userQuery);
|
||||
user = await Meteor.users.findOneAsync(userQuery);
|
||||
}
|
||||
|
||||
// Attempt to find user by e-mail address only
|
||||
|
|
@ -159,7 +159,7 @@ Accounts.registerLoginHandler('ldap', function(loginRequest) {
|
|||
|
||||
log_debug('userQuery', userQuery);
|
||||
|
||||
user = Meteor.users.findOne(userQuery);
|
||||
user = await Meteor.users.findOneAsync(userQuery);
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -182,15 +182,15 @@ Accounts.registerLoginHandler('ldap', function(loginRequest) {
|
|||
if (LDAP.settings_get('LDAP_SYNC_ADMIN_STATUS') === true) {
|
||||
log_debug('Updating admin status');
|
||||
const targetGroups = LDAP.settings_get('LDAP_SYNC_ADMIN_GROUPS').split(',');
|
||||
const groups = ldap.getUserGroups(username, ldapUser).filter((value) => targetGroups.includes(value));
|
||||
const groups = (await ldap.getUserGroups(username, ldapUser)).filter((value) => targetGroups.includes(value));
|
||||
|
||||
user.isAdmin = groups.length > 0;
|
||||
Meteor.users.update({_id: user._id}, {$set: {isAdmin: user.isAdmin}});
|
||||
await Meteor.users.updateAsync({_id: user._id}, {$set: {isAdmin: user.isAdmin}});
|
||||
}
|
||||
|
||||
if( LDAP.settings_get('LDAP_SYNC_GROUP_ROLES') === true ) {
|
||||
log_debug('Updating Groups/Roles');
|
||||
const groups = ldap.getUserGroups(username, ldapUser);
|
||||
const groups = await ldap.getUserGroups(username, ldapUser);
|
||||
|
||||
if( groups.length > 0 ) {
|
||||
Roles.setUserRoles(user._id, groups );
|
||||
|
|
@ -198,12 +198,12 @@ Accounts.registerLoginHandler('ldap', function(loginRequest) {
|
|||
}
|
||||
}
|
||||
|
||||
Meteor.users.update(user._id, update_data );
|
||||
await Meteor.users.updateAsync(user._id, update_data );
|
||||
|
||||
syncUserData(user, ldapUser);
|
||||
await syncUserData(user, ldapUser);
|
||||
|
||||
if (LDAP.settings_get('LDAP_LOGIN_FALLBACK') === true) {
|
||||
Accounts.setPassword(user._id, loginRequest.ldapPass, {logout: false});
|
||||
await Accounts.setPasswordAsync(user._id, loginRequest.ldapPass, {logout: false});
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
@ -224,19 +224,19 @@ Accounts.registerLoginHandler('ldap', function(loginRequest) {
|
|||
loginRequest.ldapPass = undefined;
|
||||
}
|
||||
|
||||
const result = addLdapUser(ldapUser, username, loginRequest.ldapPass);
|
||||
const result = await addLdapUser(ldapUser, username, loginRequest.ldapPass);
|
||||
|
||||
if (LDAP.settings_get('LDAP_SYNC_ADMIN_STATUS') === true) {
|
||||
log_debug('Updating admin status');
|
||||
const targetGroups = LDAP.settings_get('LDAP_SYNC_ADMIN_GROUPS').split(',');
|
||||
const groups = ldap.getUserGroups(username, ldapUser).filter((value) => targetGroups.includes(value));
|
||||
const groups = (await ldap.getUserGroups(username, ldapUser)).filter((value) => targetGroups.includes(value));
|
||||
|
||||
result.isAdmin = groups.length > 0;
|
||||
Meteor.users.update({_id: result.userId}, {$set: {isAdmin: result.isAdmin}});
|
||||
await Meteor.users.updateAsync({_id: result.userId}, {$set: {isAdmin: result.isAdmin}});
|
||||
}
|
||||
|
||||
if( LDAP.settings_get('LDAP_SYNC_GROUP_ROLES') === true ) {
|
||||
const groups = ldap.getUserGroups(username, ldapUser);
|
||||
const groups = await ldap.getUserGroups(username, ldapUser);
|
||||
if( groups.length > 0 ) {
|
||||
Roles.setUserRoles(result.userId, groups );
|
||||
log_info(`Set roles to:${ groups.join(',')}`);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import _ from 'underscore';
|
||||
import { SyncedCron } from 'meteor/quave:synced-cron';
|
||||
import limax from 'limax';
|
||||
import LDAP from './ldap';
|
||||
import { log_debug, log_info, log_warn, log_error } from './logger';
|
||||
|
||||
|
|
@ -20,7 +21,7 @@ export function slug(text) {
|
|||
if (LDAP.settings_get('LDAP_UTF8_NAMES_SLUGIFY') !== true) {
|
||||
return text;
|
||||
}
|
||||
text = slugify(text, '.');
|
||||
text = limax(text, { separator: '.' });
|
||||
return text.replace(/[^0-9a-z-_.]/g, '');
|
||||
}
|
||||
|
||||
|
|
@ -230,7 +231,7 @@ export function getDataToSyncUserData(ldapUser, user) {
|
|||
}
|
||||
|
||||
|
||||
export function syncUserData(user, ldapUser) {
|
||||
export async function syncUserData(user, ldapUser) {
|
||||
log_info('Syncing user data');
|
||||
log_debug('user', {'email': user.email, '_id': user._id});
|
||||
// log_debug('ldapUser', ldapUser.object);
|
||||
|
|
@ -239,7 +240,7 @@ export function syncUserData(user, ldapUser) {
|
|||
const username = slug(getLdapUsername(ldapUser));
|
||||
if (user && user._id && username !== user.username) {
|
||||
log_info('Syncing user username', user.username, '->', username);
|
||||
Meteor.users.findOne({ _id: user._id }, { $set: { username }});
|
||||
await Meteor.users.findOneAsync({ _id: user._id }, { $set: { username }});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -248,7 +249,7 @@ export function syncUserData(user, ldapUser) {
|
|||
log_debug('fullname=',fullname);
|
||||
if (user && user._id && fullname !== '') {
|
||||
log_info('Syncing user fullname:', fullname);
|
||||
Meteor.users.update({ _id: user._id }, { $set: { 'profile.fullname' : fullname, }});
|
||||
await Meteor.users.updateAsync({ _id: user._id }, { $set: { 'profile.fullname' : fullname, }});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -258,7 +259,7 @@ export function syncUserData(user, ldapUser) {
|
|||
|
||||
if (user && user._id && email !== '') {
|
||||
log_info('Syncing user email:', email);
|
||||
Meteor.users.update({
|
||||
await Meteor.users.updateAsync({
|
||||
_id: user._id
|
||||
}, {
|
||||
$set: {
|
||||
|
|
@ -270,7 +271,7 @@ export function syncUserData(user, ldapUser) {
|
|||
|
||||
}
|
||||
|
||||
export function addLdapUser(ldapUser, username, password) {
|
||||
export async function addLdapUser(ldapUser, username, password) {
|
||||
const uniqueId = getLdapUserUniqueID(ldapUser);
|
||||
|
||||
const userObject = {
|
||||
|
|
@ -307,10 +308,10 @@ export function addLdapUser(ldapUser, username, password) {
|
|||
try {
|
||||
// This creates the account with password service
|
||||
userObject.ldap = true;
|
||||
userObject._id = Accounts.createUser(userObject);
|
||||
userObject._id = await Accounts.createUserAsync(userObject);
|
||||
|
||||
// Add the services.ldap identifiers
|
||||
Meteor.users.update({ _id: userObject._id }, {
|
||||
await Meteor.users.updateAsync({ _id: userObject._id }, {
|
||||
$set: {
|
||||
'services.ldap': { id: uniqueId.value },
|
||||
'emails.0.verified': true,
|
||||
|
|
@ -321,14 +322,14 @@ export function addLdapUser(ldapUser, username, password) {
|
|||
return error;
|
||||
}
|
||||
|
||||
syncUserData(userObject, ldapUser);
|
||||
await syncUserData(userObject, ldapUser);
|
||||
|
||||
return {
|
||||
userId: userObject._id,
|
||||
};
|
||||
}
|
||||
|
||||
export function importNewUsers(ldap) {
|
||||
export async function importNewUsers(ldap) {
|
||||
if (LDAP.settings_get('LDAP_ENABLE') !== true) {
|
||||
log_error('Can\'t run LDAP Import, LDAP is disabled');
|
||||
return;
|
||||
|
|
@ -336,65 +337,57 @@ export function importNewUsers(ldap) {
|
|||
|
||||
if (!ldap) {
|
||||
ldap = new LDAP();
|
||||
ldap.connectSync();
|
||||
await ldap.connect();
|
||||
}
|
||||
|
||||
let count = 0;
|
||||
ldap.searchUsersSync('*', Meteor.bindEnvironment((error, ldapUsers, {next, end} = {}) => {
|
||||
if (error) {
|
||||
throw error;
|
||||
const ldapUsers = await ldap.searchUsers('*');
|
||||
|
||||
for (const ldapUser of ldapUsers) {
|
||||
count++;
|
||||
|
||||
const uniqueId = getLdapUserUniqueID(ldapUser);
|
||||
// Look to see if user already exists
|
||||
const userQuery = {
|
||||
'services.ldap.id': uniqueId.value,
|
||||
};
|
||||
|
||||
log_debug('userQuery', userQuery);
|
||||
|
||||
let username;
|
||||
if (LDAP.settings_get('LDAP_USERNAME_FIELD') !== '') {
|
||||
username = slug(getLdapUsername(ldapUser));
|
||||
}
|
||||
|
||||
ldapUsers.forEach((ldapUser) => {
|
||||
count++;
|
||||
// Add user if it was not added before
|
||||
let user = await Meteor.users.findOneAsync(userQuery);
|
||||
|
||||
const uniqueId = getLdapUserUniqueID(ldapUser);
|
||||
// Look to see if user already exists
|
||||
if (!user && username && LDAP.settings_get('LDAP_MERGE_EXISTING_USERS') === true) {
|
||||
const userQuery = {
|
||||
'services.ldap.id': uniqueId.value,
|
||||
username,
|
||||
};
|
||||
|
||||
log_debug('userQuery', userQuery);
|
||||
log_debug('userQuery merge', userQuery);
|
||||
|
||||
let username;
|
||||
if (LDAP.settings_get('LDAP_USERNAME_FIELD') !== '') {
|
||||
username = slug(getLdapUsername(ldapUser));
|
||||
user = await Meteor.users.findOneAsync(userQuery);
|
||||
if (user) {
|
||||
await syncUserData(user, ldapUser);
|
||||
}
|
||||
|
||||
// Add user if it was not added before
|
||||
let user = Meteor.users.findOne(userQuery);
|
||||
|
||||
if (!user && username && LDAP.settings_get('LDAP_MERGE_EXISTING_USERS') === true) {
|
||||
const userQuery = {
|
||||
username,
|
||||
};
|
||||
|
||||
log_debug('userQuery merge', userQuery);
|
||||
|
||||
user = Meteor.users.findOne(userQuery);
|
||||
if (user) {
|
||||
syncUserData(user, ldapUser);
|
||||
}
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
addLdapUser(ldapUser, username);
|
||||
}
|
||||
|
||||
if (count % 100 === 0) {
|
||||
log_info('Import running. Users imported until now:', count);
|
||||
}
|
||||
});
|
||||
|
||||
if (end) {
|
||||
log_info('Import finished. Users imported:', count);
|
||||
}
|
||||
|
||||
next(count);
|
||||
}));
|
||||
if (!user) {
|
||||
await addLdapUser(ldapUser, username);
|
||||
}
|
||||
|
||||
if (count % 100 === 0) {
|
||||
log_info('Import running. Users imported until now:', count);
|
||||
}
|
||||
}
|
||||
|
||||
log_info('Import finished. Users imported:', count);
|
||||
}
|
||||
|
||||
function sync() {
|
||||
async function sync() {
|
||||
if (LDAP.settings_get('LDAP_ENABLE') !== true) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -402,7 +395,7 @@ function sync() {
|
|||
const ldap = new LDAP();
|
||||
|
||||
try {
|
||||
ldap.connectSync();
|
||||
await ldap.connect();
|
||||
|
||||
let users;
|
||||
if (LDAP.settings_get('LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED') === true) {
|
||||
|
|
@ -410,25 +403,25 @@ function sync() {
|
|||
}
|
||||
|
||||
if (LDAP.settings_get('LDAP_BACKGROUND_SYNC_IMPORT_NEW_USERS') === true) {
|
||||
importNewUsers(ldap);
|
||||
await importNewUsers(ldap);
|
||||
}
|
||||
|
||||
if (LDAP.settings_get('LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED') === true) {
|
||||
users.forEach(function(user) {
|
||||
for await (const user of users) {
|
||||
let ldapUser;
|
||||
|
||||
if (user.services && user.services.ldap && user.services.ldap.id) {
|
||||
ldapUser = ldap.getUserByIdSync(user.services.ldap.id, user.services.ldap.idAttribute);
|
||||
ldapUser = await ldap.getUserById(user.services.ldap.id, user.services.ldap.idAttribute);
|
||||
} else {
|
||||
ldapUser = ldap.getUserByUsernameSync(user.username);
|
||||
ldapUser = await ldap.getUserByUsername(user.username);
|
||||
}
|
||||
|
||||
if (ldapUser) {
|
||||
syncUserData(user, ldapUser);
|
||||
await syncUserData(user, ldapUser);
|
||||
} else {
|
||||
log_info('Can\'t sync user', user.username);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
log_error(error);
|
||||
|
|
@ -459,8 +452,8 @@ const addCronJob = _.debounce(Meteor.bindEnvironment(function addCronJobDebounce
|
|||
else {
|
||||
return parser.recur().on(0).minute();
|
||||
}},
|
||||
job: function() {
|
||||
sync();
|
||||
job: async function() {
|
||||
await sync();
|
||||
},
|
||||
});
|
||||
sc.start();
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import {importNewUsers} from './sync';
|
|||
import LDAP from './ldap';
|
||||
|
||||
Meteor.methods({
|
||||
ldap_sync_now() {
|
||||
async ldap_sync_now() {
|
||||
const user = Meteor.user();
|
||||
if (!user) {
|
||||
throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'ldap_sync_users' });
|
||||
|
|
@ -18,7 +18,7 @@ Meteor.methods({
|
|||
|
||||
this.unblock();
|
||||
|
||||
importNewUsers();
|
||||
await importNewUsers();
|
||||
|
||||
return {
|
||||
message: 'Sync_in_progress',
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import LDAP from './ldap';
|
||||
|
||||
Meteor.methods({
|
||||
ldap_test_connection() {
|
||||
async ldap_test_connection() {
|
||||
const user = Meteor.user();
|
||||
if (!user) {
|
||||
throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'ldap_test_connection' });
|
||||
|
|
@ -12,21 +12,21 @@ Meteor.methods({
|
|||
// throw new Meteor.Error('error-not-authorized', 'Not authorized', { method: 'ldap_test_connection' });
|
||||
//}
|
||||
|
||||
if (LDAP.settings_get(LDAP_ENABLE) !== true) {
|
||||
if (LDAP.settings_get('LDAP_ENABLE') !== true) {
|
||||
throw new Meteor.Error('LDAP_disabled');
|
||||
}
|
||||
|
||||
let ldap;
|
||||
try {
|
||||
ldap = new LDAP();
|
||||
ldap.connectSync();
|
||||
await ldap.connect();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
throw new Meteor.Error(error.message);
|
||||
}
|
||||
|
||||
try {
|
||||
ldap.bindIfNecessary();
|
||||
await ldap.bindIfNecessary();
|
||||
} catch (error) {
|
||||
throw new Meteor.Error(error.name || error.message);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue