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 = { '1': { name: 'Staff', id: 1, parentid: null, }, }; 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) { 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) { 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; }).filter(u => { if (!(u.orgEmail || u.email)) { log.warn('Incorrect user missing email', u); return false; } return true; }).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) { 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, };