node-ldap/lib/providers/dingtalk.js

293 lines
7.0 KiB
JavaScript

const axios = require('axios');
const log = require('log').get('dingtalk-provider');
const {
makeGroupEntry,
makePersonEntry,
makeOrganizationUnitEntry,
addMemberToGroup,
} = require('../utilities/ldap');
const {
saveCacheToFile,
loadCacheFromFile,
} = require('../utilities/cache');
let appKey = '';
let appSecret = '';
let accessToken = '';
let allLDAPUsers = [];
let allLDAPOrgUnits = [];
let allLDAPGroups = [];
let allLDAPEntries = [];
function api(path) {
return `https://oapi.dingtalk.com/${path}?access_token=${accessToken}`;
}
function parseName(name) {
// 如果名字没有空格,以中文处理,第一个字为姓,其他为名
// 如果有空格,以英文处理,最后一个单词为姓,其他为名
let givenName = name.substr(1);
let sn = name.substr(0, 1);
if (name.indexOf(' ') > 0) {
const parts = name.split(' ');
sn = parts.pop();
givenName = parts.join(' ');
}
return { givenName, sn };
}
async function ddGet(path, params) {
const apiUrl = path.substr(0, 8) === 'https://' ? path : api(path);
const ret = await axios(apiUrl, { params }).catch(e => {
log.error("Dingtalk API error", e);
return null;
});
if (ret && ret.data) {
if (ret.data.errcode != 0) {
log.error('Dingtalk API error', ret.data);
return null;
} else {
return ret.data;
}
} else {
log.error('Dingtalk API error', ret);
}
return null;
}
async function getToken() {
const token = await ddGet(`https://oapi.dingtalk.com/gettoken?appkey=${appKey}&appsecret=${appSecret}`);
if (token && token.access_token) {
accessToken = token.access_token;
} else {
log.error('Get access token failed', token);
}
}
/*
{
'100560627': {
ext: '{"faceCount":"92"}',
createDeptGroup: true,
name: 'Product & Dev / 产品技术',
id: 100560627,
autoAddUser: true,
parentid: 111865024,
dn: 'ou=Product & Dev / 产品技术, ou=全员, o=LongBridge, dc=longbridge-inc, dc=com'
},
*/
async function fetchAllDepartments() {
let allDeps = loadCacheFromFile('dingtalk_groups.json');
if (!allDeps) {
const deps = await ddGet('department/list', {
fetch_child: true,
id: 1,
});
if (!deps) {
return [];
}
log.info('Got', deps.department.length, 'departments');
const depsMap = {};
deps.department.forEach(d => {
d.name = d.name.replace(/ \/ /g, ' - ').replace(/\//g, '&').trim();
depsMap[d.id] = d;
});
allDeps = Object.values(depsMap);
saveCacheToFile('dingtalk_groups.json', allDeps);
}
const depsMap = {};
allDeps.forEach(d => { depsMap[d.id] = d; });
allDeps.forEach(d => {
let obj = d;
let dn = [ `ou=${obj.name}` ];
while (obj.parentid !== 1) {
obj = depsMap[obj.parentid];
dn.push(`ou=${obj.name}`);
}
d.dn = dn.join(',');
});
return allDeps;
}
/*
{
unionid: 'DLB1ru0iiZ6rB8z4juaJwWAiEiE',
openId: 'DLB1ru0iiZ6rB8z4juaJwWAiEiE',
remark: '',
userid: '0156594846753096123',
isBoss: false,
hiredDate: 1539532800000,
tel: '',
department: [ 109334341 ],
workPlace: '杭州',
email: '',
order: 177917621779460500,
isLeader: false,
mobile: '18815286506',
active: true,
isAdmin: false,
avatar: 'https://static-legacy.dingtalk.com/media/lADPACOG819UTLzNAu7NAu4_750_750.jpg',
isHide: false,
orgEmail: 'ke.xu@longbridge.sg',
jobnumber: '0049',
name: '徐克',
extattr: {},
stateCode: '86',
position: 'iOS开发工程师'
}
*/
async function fetchDepartmentUsers(department) {
log.info('get users for department', department);
const userlist = [];
let hasMore = true;
let offset = 0;
while (hasMore) {
const users = await ddGet('user/listbypage', {
department_id: department.id,
offset,
size: 100,
order: 'entry_asc',
});
userlist.push(...users.userlist);
hasMore = users.hasMore;
}
userlist.forEach(u => {
u.firstDepartment = department;
});
return userlist;
}
async function fetchAllUsers(departments) {
let allUsers = loadCacheFromFile('dingtalk_users.json');
if (!allUsers && departments.length > 0) {
allUsers = [];
for (let i = 0; i < departments.length; ++i) {
allUsers.push(...(await fetchDepartmentUsers(departments[i])));
}
saveCacheToFile('dingtalk_users.json', allUsers);
}
return allUsers;
}
async function setupProvider(config) {
appKey = config.appKey;
appSecret = config.appSecret;
await reloadFromDingtalkServer();
}
async function reloadFromDingtalkServer() {
await getToken();
// 获取所有部门
let allDepartments = await fetchAllDepartments();
// 映射到 organizationalUnit
const allDepartmentsMap = {};
allLDAPOrgUnits = allDepartments.map(d => {
allDepartmentsMap[d.id] = d;
return makeOrganizationUnitEntry(d.dn, d.name, {
groupid: d.id,
});
});
// 映射到 groupOfNames
const allLDAPGroupsMap = [];
allLDAPGroups = allDepartments.map(d => {
const g = makeGroupEntry(d.dn, d.name, [], {
groupid: d.id,
});
allLDAPGroupsMap[d.id] = g;
return g;
});
Object.values(allDepartmentsMap).forEach(dep => {
if (dep.parentid != 1) {
const parentDep = allDepartmentsMap[dep.parentid];
addMemberToGroup(allLDAPGroupsMap[dep.id], allLDAPGroupsMap[parentDep.id]);
}
})
// 按部门获取所有员工
const allUsers = await fetchAllUsers(allDepartments);
const allUsersMap = {};
allLDAPUsers = allUsers.filter(u => {
if (!allUsersMap[u.userid]) {
allUsersMap[u.userid] = 1;
return u.active;
}
return false;
}).map(u => {
const mail = (u.orgEmail || u.email).toLowerCase();
const dn = `mail=${mail},${u.firstDepartment.dn}`;
const { givenName, sn } = parseName(u.name);
// 映射到 iNetOrgPerson
const personEntry = makePersonEntry(dn, {
uid: u.userid,
title: u.position,
mobileTelephoneNumber: u.mobile,
cn: u.name,
givenName,
sn,
mail,
avatarurl: u.avatar,
});
// 将用户加到组里
u.department.forEach(depId => {
let parentDep = allDepartmentsMap[depId];
// allLDAPGroupsMap[parentDep.id].attributes.member.push(personEntry.dn);
while (parentDep && parentDep.id !== 1) {
addMemberToGroup(personEntry, allLDAPGroupsMap[parentDep.id]);
// console.log('add member', personEntry.attributes.cn, 'to', allLDAPGroupsMap[parentDep.id].attributes.cn);
parentDep = allDepartmentsMap[parentDep.parentid];
}
})
return personEntry;
});
allLDAPEntries = [].concat(allLDAPGroups, allLDAPOrgUnits, allLDAPUsers);
}
function getAllLDAPEntries() {
return allLDAPEntries;
}
function reloadEntriesFromProvider() {
log.info('Reload entries from Dingtalk');
reloadFromDingtalkServer();
}
// if (0) {
// (async function() {
// await setupProvider(require('../config').provider);
// log.info(getAllLDAPEntries());
// })();
// setTimeout(() => {}, 0);
// }
module.exports = {
setupProvider,
getAllLDAPEntries,
reloadEntriesFromProvider,
};