305 lines
7.2 KiB
JavaScript
305 lines
7.2 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 = {
|
|
'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,
|
|
};
|