275 lines
9.4 KiB
JavaScript
275 lines
9.4 KiB
JavaScript
const ldap = require('ldapjs');
|
|
const fs = require('fs');
|
|
const csv = require('csv-parser');
|
|
const { Parser } = require('json2csv');
|
|
const iconv = require('iconv-lite'); // 追加
|
|
|
|
const LDAP_URL = 'ldap://192.168.4.225';
|
|
const BASE_DN = 'dc=ldap,dc=next-hd,dc=co,dc=jp'; // ルートに変更
|
|
const EXPORT_CSV = 'ldap_users_export.csv';
|
|
const IMPORT_CSV = 'ldap_users_import.csv';
|
|
|
|
const BIND_DN = 'uid=root,cn=users,dc=ldap,dc=next-hd,dc=co,dc=jp'; // 管理者DN
|
|
const BIND_PASSWORD = 'il7DXhKA'; // 管理者パスワード
|
|
|
|
// 管理者権限でLDAP接続
|
|
function getLdapClientAndBind(callback) {
|
|
const client = ldap.createClient({ url: LDAP_URL });
|
|
client.bind(BIND_DN, BIND_PASSWORD, err => {
|
|
callback(client, err);
|
|
});
|
|
}
|
|
|
|
// LDAPからデータを取得してCSV出力
|
|
function exportLdapToCsv() {
|
|
getLdapClientAndBind((client, err) => {
|
|
if (err) {
|
|
console.error('LDAP bind error:', err);
|
|
client.unbind();
|
|
return;
|
|
}
|
|
const entries = [];
|
|
// filterでcn=users配下のユーザーのみ取得
|
|
client.search(BASE_DN, {
|
|
scope: 'sub',
|
|
filter: 'objectClass=person',
|
|
attributes: []
|
|
}, (err, res) => {
|
|
if (err) {
|
|
console.error('LDAP search error:', err);
|
|
client.unbind();
|
|
return;
|
|
}
|
|
res.on('searchEntry', entry => {
|
|
const obj = {};
|
|
if (Array.isArray(entry.attributes)) {
|
|
entry.attributes.forEach(attr => {
|
|
obj[attr.type] = Array.isArray(attr.vals) ? attr.vals.join(',') : attr.vals;
|
|
});
|
|
entries.push(obj);
|
|
}
|
|
});
|
|
res.on('end', () => {
|
|
if (entries.length === 0) {
|
|
console.log('エントリが見つかりませんでした。');
|
|
} else {
|
|
console.log(`取得したエントリ数: ${entries.length}`);
|
|
console.log('エントリの例:', entries[100]);
|
|
// 属性値が配列の場合は文字列化
|
|
const normalized = entries.map(obj => {
|
|
const n = {};
|
|
if (obj && typeof obj === 'object') {
|
|
Object.keys(obj).forEach(k => {
|
|
if (Array.isArray(obj[k])) {
|
|
n[k] = obj[k].join(','); // カンマ区切りで連結
|
|
} else {
|
|
n[k] = obj[k];
|
|
}
|
|
});
|
|
}
|
|
return n;
|
|
});
|
|
// 取得できた属性名を自動で抽出
|
|
const allFields = Array.from(
|
|
normalized.reduce((set, obj) => {
|
|
if (obj && typeof obj === 'object') {
|
|
Object.keys(obj).forEach(k => set.add(k));
|
|
}
|
|
return set;
|
|
}, new Set())
|
|
);
|
|
const parser = new Parser({ fields: allFields });
|
|
const csvData = parser.parse(normalized);
|
|
// Shift-JISに変換して保存
|
|
const sjisData = iconv.encode(csvData, 'Shift_JIS');
|
|
fs.writeFileSync(EXPORT_CSV, sjisData);
|
|
console.log('LDAPデータをShift-JISでCSVにエクスポートしました:', EXPORT_CSV);
|
|
}
|
|
client.unbind();
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
// CSVを読み込んでLDAPを更新
|
|
function importCsvToLdap() {
|
|
const updates = [];
|
|
fs.createReadStream(IMPORT_CSV)
|
|
.pipe(csv())
|
|
.on('data', (row) => {
|
|
updates.push(row);
|
|
})
|
|
.on('end', () => {
|
|
getLdapClientAndBind((client, err) => {
|
|
if (err) {
|
|
console.error('LDAP bind error:', err);
|
|
client.unbind();
|
|
return;
|
|
}
|
|
let completed = 0;
|
|
updates.forEach(update => {
|
|
const uidNumber = update.uidNumber;
|
|
if (uidNumber) {
|
|
// uidNumberがある場合は検索して更新
|
|
client.search(BASE_DN, {
|
|
scope: 'sub',
|
|
filter: `(uidNumber=${uidNumber})`,
|
|
attributes: ['dn']
|
|
}, (err, res) => {
|
|
if (err) {
|
|
console.error('LDAP search error:', err);
|
|
completed++;
|
|
if (completed === updates.length) client.unbind();
|
|
return;
|
|
}
|
|
let found = false;
|
|
res.on('searchEntry', entry => {
|
|
found = true;
|
|
const dn = entry.objectName || entry.dn || (entry.object && entry.object.dn);
|
|
if (!dn) {
|
|
console.error('dnが取得できませんでした:', entry);
|
|
completed++;
|
|
if (completed === updates.length) client.unbind();
|
|
return;
|
|
}
|
|
// dnとobjectClass以外の属性を更新
|
|
const changes = [];
|
|
Object.keys(update).forEach(attr => {
|
|
if (attr !== 'dn' && attr !== 'objectClass' && update[attr]) {
|
|
changes.push(new ldap.Change({
|
|
operation: 'replace',
|
|
modification: { [attr]: update[attr] }
|
|
}));
|
|
}
|
|
});
|
|
if (changes.length > 0) {
|
|
client.modify(dn, changes, err => {
|
|
if (err) {
|
|
console.error(`LDAP modify error for ${dn}:`, err);
|
|
} else {
|
|
console.log(`LDAPエントリを更新しました: ${dn}`);
|
|
}
|
|
completed++;
|
|
if (completed === updates.length) client.unbind();
|
|
});
|
|
} else {
|
|
completed++;
|
|
if (completed === updates.length) client.unbind();
|
|
}
|
|
});
|
|
res.on('end', () => {
|
|
if (!found) {
|
|
// uidNumberはあるが該当エントリがない場合は新規登録
|
|
addLdapEntry(client, update, () => {
|
|
completed++;
|
|
if (completed === updates.length) client.unbind();
|
|
});
|
|
}
|
|
});
|
|
});
|
|
} else {
|
|
// uidNumberがない場合は新規登録
|
|
addLdapEntry(client, update, () => {
|
|
completed++;
|
|
if (completed === updates.length) client.unbind();
|
|
});
|
|
}
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
// 新規登録用ヘルパー関数
|
|
function addLdapEntry(client, entry, callback) {
|
|
// 必要な属性を設定
|
|
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 = { ...entry, objectClass };
|
|
delete attrs.dn;
|
|
client.add(dn, attrs, err => {
|
|
if (err) {
|
|
console.error(`LDAPエントリ新規登録失敗: ${dn}`, err);
|
|
} else {
|
|
console.log(`LDAPエントリ新規登録: ${dn}`);
|
|
}
|
|
if (callback) callback();
|
|
});
|
|
}
|
|
|
|
// 指定したuidNumberのエントリーを削除する関数
|
|
function deleteEntriesByUidNumbers(uidNumbers) {
|
|
if (!Array.isArray(uidNumbers) || uidNumbers.length === 0) {
|
|
console.log('uidNumberの配列を指定してください。');
|
|
return;
|
|
}
|
|
getLdapClientAndBind((client, err) => {
|
|
if (err) {
|
|
console.error('LDAP bind error:', err);
|
|
client.unbind();
|
|
return;
|
|
}
|
|
// 各uidNumberごとに削除処理
|
|
let processed = 0;
|
|
uidNumbers.forEach(uidNumber => {
|
|
// uidNumberでエントリーを検索
|
|
client.search(BASE_DN, {
|
|
scope: 'sub',
|
|
filter: `(uidNumber=${uidNumber})`,
|
|
attributes: ['dn']
|
|
}, (err, res) => {
|
|
if (err) {
|
|
console.error('LDAP search error:', err);
|
|
processed++;
|
|
if (processed === uidNumbers.length) client.unbind();
|
|
return;
|
|
}
|
|
let found = false;
|
|
res.on('searchEntry', entry => {
|
|
found = true;
|
|
const dn = entry.objectName || entry.dn || (entry.object && entry.object.dn);
|
|
if (dn) {
|
|
client.del(dn, err => {
|
|
if (err) {
|
|
console.error(`削除失敗: ${dn}`, err);
|
|
} else {
|
|
console.log(`削除成功: ${dn}`);
|
|
}
|
|
processed++;
|
|
if (processed === uidNumbers.length) client.unbind();
|
|
});
|
|
} else {
|
|
console.error('dnが取得できませんでした:', entry);
|
|
processed++;
|
|
if (processed === uidNumbers.length) client.unbind();
|
|
}
|
|
});
|
|
res.on('end', () => {
|
|
if (!found) {
|
|
console.log(`uidNumber=${uidNumber} のエントリーが見つかりませんでした。`);
|
|
processed++;
|
|
if (processed === uidNumbers.length) client.unbind();
|
|
}
|
|
});
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
// コマンドライン引数で処理を切り替え
|
|
const mode = process.argv[2];
|
|
if (mode === 'export') {
|
|
exportLdapToCsv();
|
|
} else if (mode === 'import') {
|
|
importCsvToLdap();
|
|
} else if (mode === 'delete-uids') {
|
|
// 例: node ldapUpdate.js delete-uids 1000003,1000004
|
|
const uidNumbers = (process.argv[3] || '').split(',').map(s => s.trim()).filter(Boolean);
|
|
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のエントリーを削除');
|
|
} |