ken_nogi/NodeJS/LDAP/ldapUpdate_v2.js
Kenichiro NOGI 88a402ce0f up
2026-07-10 18:13:30 +09:00

370 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const ldap = require('ldapjs');
const fs = require('fs');
const csv = require('csv-parser');
const { Parser } = require('json2csv');
const iconv = require('iconv-lite'); // Shift-JIS 変換用
const LDAP_URL = 'ldap://192.168.4.225';
const BASE_DN = 'dc=ldap,dc=next-hd,dc=co,dc=jp';
const BIND_DN = 'uid=root,cn=users,dc=ldap,dc=next-hd,dc=co,dc=jp';
const BIND_PASSWORD = 'il7DXhKA';
function padTwo(n) {
return String(n).padStart(2, '0');
}
// 現在日時からエクスポート用タイムスタンプ文字列を生成
function createTimestamp() {
const now = new Date();
return `${now.getFullYear()}${padTwo(now.getMonth() + 1)}${padTwo(now.getDate())}_${padTwo(now.getHours())}${padTwo(now.getMinutes())}${padTwo(now.getSeconds())}`;
}
const EXPORT_CSV = `ldap_users_export_${createTimestamp()}.csv`;
const IMPORT_CSV = 'ldap_users_import.csv';
// LDAP クライアントに bind して認証を完了する Promise
function bindClient(client) {
return new Promise(function (resolve, reject) {
client.bind(BIND_DN, BIND_PASSWORD, function (err) {
if (err) {
reject(err);
return;
}
resolve();
});
});
}
// bind 済みの LDAP クライアントを生成
async function createBoundClient() {
const client = ldap.createClient({ url: LDAP_URL });
await bindClient(client);
return client;
}
// searchEntry から通常オブジェクトに変換
function entryToObject(entry) {
if (!entry || !Array.isArray(entry.attributes)) {
return null;
}
const obj = {};
entry.attributes.forEach(function (attr) {
obj[attr.type] = Array.isArray(attr.vals) ? attr.vals.join(',') : attr.vals;
});
return obj;
}
// LDAP 検索を行いエントリ配列を取得
function searchEntries(client, options) {
return new Promise(function (resolve, reject) {
client.search(BASE_DN, options, function (err, res) {
if (err) {
reject(err);
return;
}
const results = [];
res.on('searchEntry', function (entry) {
results.push(entry);
});
res.on('error', function (searchErr) {
reject(searchErr);
});
res.on('end', function () {
resolve(results);
});
});
});
}
// エントリの属性を変更
function modifyEntry(client, dn, changes) {
return new Promise(function (resolve, reject) {
client.modify(dn, changes, function (err) {
if (err) {
reject(err);
return;
}
resolve();
});
});
}
// エントリを追加
function addEntry(client, dn, attrs) {
return new Promise(function (resolve, reject) {
client.add(dn, attrs, function (err) {
if (err) {
reject(err);
return;
}
resolve();
});
});
}
// エントリを削除
function deleteEntry(client, dn) {
return new Promise(function (resolve, reject) {
client.del(dn, function (err) {
if (err) {
reject(err);
return;
}
resolve();
});
});
}
// CSV ファイルを読み込み行配列を返す
function readCsvRows(filePath) {
return new Promise(function (resolve, reject) {
const rows = [];
const stream = fs.createReadStream(filePath)
.pipe(iconv.decodeStream('Shift_JIS')) // 読み込み時に SJIS → UTF-8 へ変換
.pipe(csv());
stream.on('data', function (row) {
rows.push(row);
});
stream.on('end', function () {
resolve(rows);
});
stream.on('error', function (err) {
reject(err);
});
});
}
// 更新行から LDAP modify 用の Change 配列を生成
function buildChangesFromUpdate(update) {
const changes = [];
const keys = Object.keys(update);
for (let i = 0; i < keys.length; i += 1) {
const attr = keys[i];
// dn/objectClass は除外し、RDN となる uid も modify 対象から外す
if (attr !== 'dn' && attr !== 'objectClass' && attr !== 'uid' && update[attr]) {
const values = Array.isArray(update[attr]) ? update[attr] : [String(update[attr])];
const modification = new ldap.Attribute({ type: attr, values: values });
changes.push(new ldap.Change({
operation: 'replace',
modification: modification
}));
}
}
return changes;
}
// searchEntry から DN を抽出
function getDnFromSearchEntry(entry) {
if (!entry) {
return null;
}
return entry.objectName || entry.dn || (entry.object && entry.object.dn) || null;
}
// LDAP エントリを新規追加
async function addLdapEntry(client, entry) {
const dn = entry.dn || `uid=${entry.uid},cn=users,${BASE_DN}`;
const objectClass = entry.objectClass ? entry.objectClass.split(',') : [
'top', 'person', 'organizationalPerson', 'inetOrgPerson', 'posixAccount', 'shadowAccount'
];
const attrs = Object.assign({}, entry, { objectClass: objectClass });
delete attrs.dn;
await addEntry(client, dn, attrs);
console.log(`LDAPエントリ新規登録: ${dn}`);
}
// uidNumber で更新 or なければ新規追加
async function upsertEntry(client, update) {
const uidNumber = update.uidNumber;
if (uidNumber) {
const results = await searchEntries(client, {
scope: 'sub',
filter: `(uidNumber=${uidNumber})`,
attributes: ['dn']
});
if (results.length > 0) {
const dn = getDnFromSearchEntry(results[0]);
if (!dn) {
console.error('dnが取得できませんでした:', results[0]);
return;
}
const changes = buildChangesFromUpdate(update);
if (changes.length > 0) {
await modifyEntry(client, dn, changes);
console.log(`LDAPエントリを更新しました: ${dn}`);
} else {
console.log(`変更対象属性がありません: ${dn}`);
}
} else {
await addLdapEntry(client, update);
}
} else {
await addLdapEntry(client, update);
}
}
// LDAP から取得し Shift-JIS CSV へエクスポート
async function exportLdapToCsv() {
let client;
try {
client = await createBoundClient();
} catch (bindErr) {
console.error('LDAP bind error:', bindErr);
return;
}
try {
const rawEntries = await searchEntries(client, {
scope: 'sub',
filter: 'objectClass=person',
attributes: []
});
const entries = rawEntries
.map(function (entry) { return entryToObject(entry); })
.filter(function (item) { return item !== null; });
if (entries.length === 0) {
console.log('エントリが見つかりませんでした。');
return;
}
const normalized = entries.map(function (obj) {
const normalizedObj = {};
if (obj && typeof obj === 'object') {
const keys = Object.keys(obj);
for (let i = 0; i < keys.length; i += 1) {
const key = keys[i];
if (Array.isArray(obj[key])) {
normalizedObj[key] = obj[key].join(',');
} else {
normalizedObj[key] = obj[key];
}
}
}
return normalizedObj;
});
const allFields = Array.from(normalized.reduce(function (set, obj) {
if (obj && typeof obj === 'object') {
const keys = Object.keys(obj);
for (let i = 0; i < keys.length; i += 1) {
set.add(keys[i]);
}
}
return set;
}, new Set()));
const parser = new Parser({ fields: allFields });
const csvData = parser.parse(normalized);
const sjisData = iconv.encode(csvData, 'Shift_JIS');
fs.writeFileSync(EXPORT_CSV, sjisData);
console.log(`LDAPデータをShift-JISでCSVにエクスポートしました: ${EXPORT_CSV}`);
} catch (err) {
console.error('LDAP search/export error:', err);
} finally {
if (client) {
client.unbind();
}
}
}
// CSV を読み込み LDAP を更新(逐次 upsert
async function importCsvToLdap() {
let updates;
try {
updates = await readCsvRows(IMPORT_CSV);
} catch (readErr) {
console.error('CSV読込エラー:', readErr);
return;
}
if (!updates || updates.length === 0) {
console.log('CSVに更新対象データがありません。');
return;
}
let client;
try {
client = await createBoundClient();
} catch (bindErr) {
console.error('LDAP bind error:', bindErr);
return;
}
try {
for (let i = 0; i < updates.length; i += 1) {
await upsertEntry(client, updates[i]);
}
} catch (err) {
console.error('LDAP import error:', err);
} finally {
if (client) {
client.unbind();
}
}
}
// 指定 uidNumber のエントリを削除
async function deleteEntriesByUidNumbers(uidNumbers) {
if (!Array.isArray(uidNumbers) || uidNumbers.length === 0) {
console.log('uidNumberの配列を指定してください。');
return;
}
let client;
try {
client = await createBoundClient();
} catch (bindErr) {
console.error('LDAP bind error:', bindErr);
return;
}
try {
for (let i = 0; i < uidNumbers.length; i += 1) {
const uidNumber = uidNumbers[i];
const results = await searchEntries(client, {
scope: 'sub',
filter: `(uidNumber=${uidNumber})`,
attributes: ['dn']
});
if (results.length === 0) {
console.log(`uidNumber=${uidNumber} のエントリーが見つかりませんでした。`);
continue;
}
const dn = getDnFromSearchEntry(results[0]);
if (!dn) {
console.error('dnが取得できませんでした:', results[0]);
continue;
}
await deleteEntry(client, dn);
console.log(`削除成功: ${dn}`);
}
} catch (err) {
console.error('LDAP delete error:', err);
} finally {
if (client) {
client.unbind();
}
}
}
// コマンドライン引数に応じて処理を振り分け
async function main() {
const mode = process.argv[2];
if (mode === 'export') {
await exportLdapToCsv();
} else if (mode === 'import') {
await importCsvToLdap();
} else if (mode === 'delete-uids') {
const uidNumbers = (process.argv[3] || '').split(',').map(function (s) { return s.trim(); }).filter(function (v) { return Boolean(v); });
await deleteEntriesByUidNumbers(uidNumbers);
} else {
console.log('使い方:');
console.log(' node ldapUpdate.js export # cn=users配下のLDAPデータをCSVエクスポート');
console.log(' node ldapUpdate.js import # CSVからLDAPを更新');
console.log(' node ldapUpdate.js delete-uids 1000003,1000004 # 指定uidNumberのエントリーを削除');
}
}
main();