1 | var util = require('util'),
|
2 | https = require('https'),
|
3 | url = require('url'),
|
4 | crypto = require ('crypto'),
|
5 | crack = require("crack"),
|
6 | spawn = require('child_process').spawn;
|
7 | task = require('./base');
|
8 |
|
9 | var exchangeConfig = project.config.consumerConfig.exchange;
|
10 | var wsdlUrl = exchangeConfig.wsdlUrl;
|
11 | var servicesUrl = exchangeConfig.servicesUrl;
|
12 |
|
13 | var exchange = module.exports = function (config) {
|
14 | this.init (config);
|
15 | };
|
16 |
|
17 | util.inherits(exchange, task);
|
18 |
|
19 | util.extend(exchange.prototype, {
|
20 | run: function () {
|
21 | this.failed('use method [login|profile|check|searchUsers]');
|
22 | },
|
23 |
|
24 | login: function () {
|
25 | var self = this,
|
26 | login = self.credentials.login,
|
27 | password = self.credentials.password;
|
28 |
|
29 |
|
30 | var options = url.parse(wsdlUrl);
|
31 |
|
32 | options.method = 'GET';
|
33 |
|
34 | var processNtlmResponse = function(exit_code, err, data){
|
35 |
|
36 | switch (exit_code)
|
37 | {
|
38 | case 0:
|
39 | console.log('login completed 200');
|
40 | self.completed({
|
41 | statusCode: 200,
|
42 | err: '',
|
43 | accessAllowed: true,
|
44 | success: true
|
45 | });
|
46 | break;
|
47 | default:
|
48 | console.log('login completed 401');
|
49 | self.completed({
|
50 | statusCode: 401,
|
51 | err: 'User not authorized',
|
52 | accessAllowed: false,
|
53 | success: false
|
54 | });
|
55 | break;
|
56 | }
|
57 | };
|
58 |
|
59 | var processBasicResponse = function(response){
|
60 | switch (response.statusCode)
|
61 | {
|
62 | case 200:
|
63 | self.completed({
|
64 | statusCode: 200,
|
65 | err: '',
|
66 | accessAllowed: true,
|
67 | success: true
|
68 | });
|
69 | break;
|
70 | default:
|
71 | self.completed({
|
72 | statusCode: 401,
|
73 | err: 'User not authorized',
|
74 | accessAllowed: false,
|
75 | success: false
|
76 | });
|
77 | break;
|
78 | }
|
79 | response.destroy();
|
80 | };
|
81 |
|
82 |
|
83 | if (self.exchangeAuthType == 'ntlm') {
|
84 | this.getNtlmRequest(
|
85 | [
|
86 | wsdlUrl,
|
87 | '--ntlm',
|
88 | '-f',
|
89 | '-s',
|
90 | '-S',
|
91 | '--user',
|
92 | login + ':' + password
|
93 | ],
|
94 | processNtlmResponse
|
95 | );
|
96 | } else {
|
97 | options.auth = login + ":" + password;
|
98 | var req = this.getBasicRequest(options, processBasicResponse);
|
99 | req.on('error', function(e) {
|
100 | console.error(e);
|
101 | });
|
102 | req.end();
|
103 | }
|
104 | },
|
105 |
|
106 | getBasicRequest: function (options, responseCb) {
|
107 | var req = https.request(options, responseCb);
|
108 | return req;
|
109 | },
|
110 |
|
111 | getNtlmRequest: function (options, callback) {
|
112 |
|
113 | var curl = spawn('curl',
|
114 | options
|
115 | |
116 |
|
117 |
|
118 |
|
119 |
|
120 |
|
121 |
|
122 |
|
123 |
|
124 |
|
125 |
|
126 |
|
127 |
|
128 | ),
|
129 | data = '',
|
130 | err = null;
|
131 |
|
132 | curl.stdout.on('data', function(chunk) {
|
133 | data += chunk;
|
134 | });
|
135 |
|
136 | curl.stderr.on('data', function(err_msg) {
|
137 | if (err === null) { err = ''; }
|
138 | err += err_msg;
|
139 | });
|
140 |
|
141 | curl.on('error', function (err){console.error(err);});
|
142 |
|
143 | curl.on('exit', function(exit_code) {
|
144 | callback(exit_code, err, data);
|
145 | });
|
146 | },
|
147 |
|
148 | encode: function (str) {
|
149 | return new Buffer(str).toString('base64');
|
150 | },
|
151 |
|
152 | decode: function (str) {
|
153 | return new Buffer(str, 'base64').toString('utf8');
|
154 | },
|
155 |
|
156 | profile: function() {
|
157 | var self = this,
|
158 | ldapRequest = self.ldapResponse,
|
159 | sessionUID = self.sessionUID,
|
160 | user = ldapRequest.data && ldapRequest.data.length && ldapRequest.data[0],
|
161 | credentials = self.credentials;
|
162 |
|
163 | if (user) {
|
164 | if (Object.prototype.toString.call( user.memberof ) === '[object String]') {
|
165 |
|
166 | user.memberof = [ user.memberof ];
|
167 | } else {
|
168 |
|
169 | user.memberof = user.memberof.map(function(item) {
|
170 | return item.split(',')[0].split('=')[1];
|
171 | });
|
172 | }
|
173 |
|
174 | var result = {
|
175 | email: user.mail || user.email,
|
176 | name: user.cn || user.name,
|
177 | groupIds: user.memberof,
|
178 | sessionUIDs: sessionUID,
|
179 | authType: 'exchange',
|
180 | tokens: {
|
181 | login: credentials.login,
|
182 | password: this.encode(credentials.password)
|
183 | }
|
184 | };
|
185 |
|
186 | if (user.thumbnailphoto){
|
187 | var shasum = crypto.createHash('sha1');
|
188 | shasum.update(user.mail);
|
189 | var filePath = '/images/avatars/'+shasum.digest('hex')+'.png';
|
190 | var cacheFileStream = project.root.fileIO('htdocs'+filePath).writeStream({flags: 'w', mode: 0666});
|
191 | cacheFileStream.write(new Buffer(user.thumbnailphoto, 'base64'));
|
192 |
|
193 | result.avatar = filePath;
|
194 | }
|
195 | if (user.department){
|
196 | result.department = user.department;
|
197 | }
|
198 | if (user.division){
|
199 | result.division = user.division;
|
200 | }
|
201 |
|
202 | self.completed(result);
|
203 | } else {
|
204 | self.failed({
|
205 | statusCode: 404,
|
206 | msg: 'User Not Found!'
|
207 | });
|
208 | }
|
209 |
|
210 | },
|
211 |
|
212 | check: function() {
|
213 | var self = this,
|
214 | user = self.user;
|
215 |
|
216 | if (user && user.authType == 'exchange' && user.tokens && user.tokens.login && user.tokens.password) {
|
217 |
|
218 | self.credentials = {
|
219 | login: user.tokens.login,
|
220 | password: this.decode(user.tokens.password)
|
221 | };
|
222 |
|
223 | self.login();
|
224 |
|
225 | } else {
|
226 |
|
227 | self.completed({
|
228 | accessAllowed: true
|
229 | });
|
230 |
|
231 | }
|
232 | },
|
233 |
|
234 | tmpl: function (str, obj) {
|
235 | return str.replace(
|
236 | /{\$(.+?)}/g,
|
237 | function (_, key) { return obj[key]; }
|
238 | );
|
239 | },
|
240 |
|
241 | prepareQuery: function () {
|
242 | var queryTpl = [
|
243 | '<?xml version="1.0" encoding="utf-8"?>',
|
244 | '<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types">',
|
245 | '<soap:Body>',
|
246 | '<ResolveNames xmlns="http://schemas.microsoft.com/exchange/services/2006/messages" xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types" ReturnFullContactData="true">',
|
247 | '<UnresolvedEntry>{$filter}</UnresolvedEntry>',
|
248 | '</ResolveNames>',
|
249 | '</soap:Body>',
|
250 | '</soap:Envelope>'
|
251 | ].join(' ');
|
252 |
|
253 |
|
254 | },
|
255 |
|
256 | encodePassword: function () {
|
257 | this.completed({
|
258 | password: this.encode(this.plainPassword)
|
259 | });
|
260 | },
|
261 |
|
262 | searchUsers: function () {
|
263 | var self = this,
|
264 | user = self.user;
|
265 |
|
266 | if (user && user.authType == 'exchange' && user.tokens && user.tokens.login && user.tokens.password) {
|
267 |
|
268 | var login = user.tokens.login,
|
269 | password = this.decode(user.tokens.password);
|
270 |
|
271 | var options = url.parse(servicesUrl);
|
272 |
|
273 | var queryTpl = [
|
274 | '<?xml version="1.0" encoding="utf-8"?>',
|
275 | '<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types">',
|
276 | '<soap:Body>',
|
277 | '<ResolveNames xmlns="http://schemas.microsoft.com/exchange/services/2006/messages" xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types" ReturnFullContactData="true">',
|
278 | '<UnresolvedEntry>{$filter}</UnresolvedEntry>',
|
279 | '</ResolveNames>',
|
280 | '</soap:Body>',
|
281 | '</soap:Envelope>'
|
282 | ].join(' ');
|
283 |
|
284 | var query = this.tmpl(queryTpl, this.pager);
|
285 |
|
286 | options.auth = login + ':' + password;
|
287 | options.port = 443;
|
288 | options.headers = {
|
289 | 'Content-Type': 'text/xml'
|
290 | };
|
291 | options.method = 'POST';
|
292 |
|
293 | var processNtlmResponse = function(exit_code, err, data){
|
294 |
|
295 |
|
296 | switch (exit_code)
|
297 | {
|
298 | case 0:
|
299 | console.log('searchUsers completed 200');
|
300 |
|
301 | processData(data);
|
302 | break;
|
303 | default:
|
304 | console.log('searchUsers completed 401');
|
305 | self.completed({
|
306 | data: null,
|
307 | total: 0,
|
308 | success: false,
|
309 | error: '401 - not authorized'
|
310 | });
|
311 | break;
|
312 | }
|
313 | };
|
314 |
|
315 | var processData = function (exchangeXmlAnswer) {
|
316 | var error = '';
|
317 | var users = [];
|
318 |
|
319 | var crackedResponse = crack(exchangeXmlAnswer);
|
320 | var objResponse = crackedResponse.toJS();
|
321 |
|
322 | if (objResponse.Body.Fault) {
|
323 | console.log('!!!!!!!!!!!!!!! RESPONSE FAULT:', objResponse.Body.Fault.faultstring.__content);
|
324 | self.failed({
|
325 | statusCode: 500,
|
326 | msg: 'Response Fault!'
|
327 | });
|
328 | return;
|
329 | }
|
330 |
|
331 | var objResolveNamesResponseMessage = objResponse.Body.ResolveNamesResponse.ResponseMessages.ResolveNamesResponseMessage;
|
332 |
|
333 | if (objResolveNamesResponseMessage.ResponseClass != 'Success' && objResolveNamesResponseMessage.ResponseClass != 'Warning'){
|
334 | console.log('!!!!!!!!!!!!!!! NOT SUCCESS!');
|
335 | self.failed({
|
336 | statusCode: 500,
|
337 | msg: 'Response Not Success!'
|
338 | });
|
339 | error = 'Response Not Success!';
|
340 |
|
341 | }
|
342 |
|
343 | if (!error) {
|
344 | var objResolutionSet = objResolveNamesResponseMessage.ResolutionSet;
|
345 | if (Object.prototype.toString.call( objResolutionSet ) === '[object Array]') {
|
346 | console.log('Found: ' + objResolutionSet.TotalItemsInView);
|
347 | console.log('Query:', self.pager.filter);
|
348 | }
|
349 | if (Object.prototype.toString.call( objResolutionSet.Resolution ) === '[object Array]') {
|
350 | objResolutionSet.Resolution.forEach(function (objResolution){
|
351 | var objMailbox = objResolution.Mailbox;
|
352 | var objContact = objResolution.Contact;
|
353 | users.push({
|
354 | name: objMailbox.Name,
|
355 | authType: 'exchange',
|
356 | avatar: '',
|
357 | email: objMailbox.EmailAddress,
|
358 | _id: objMailbox.EmailAddress,
|
359 | text: objMailbox.Name,
|
360 | memberof: objContact.Department
|
361 | });
|
362 |
|
363 |
|
364 | });
|
365 | } else if (Object.prototype.toString.call( objResolutionSet.Resolution ) === '[object Object]') {
|
366 | users.push({
|
367 | name: objResolutionSet.Resolution.Mailbox.Name,
|
368 | link: undefined,
|
369 | authType: 'exchange',
|
370 | avatar: '',
|
371 | email: objResolutionSet.Resolution.Mailbox.EmailAddress,
|
372 | _id: objResolutionSet.Resolution.Mailbox.EmailAddress,
|
373 | text: objResolutionSet.Resolution.Mailbox.Name,
|
374 | memberof: objResolutionSet.Resolution.Contact.Department
|
375 | });
|
376 |
|
377 |
|
378 | }
|
379 | }
|
380 |
|
381 | self.completed({
|
382 | data: users || null,
|
383 | total: users ? users.length : 0,
|
384 | success: !error,
|
385 | error: error
|
386 | });
|
387 | }
|
388 |
|
389 | var processBasicResponse = function (response) {
|
390 | var exchangeXmlAnswer = [];
|
391 |
|
392 | response.setEncoding('utf-8');
|
393 |
|
394 | response.on('data', function (data) {
|
395 | exchangeXmlAnswer.push(data);
|
396 | });
|
397 |
|
398 | response.on('end', function () {
|
399 |
|
400 |
|
401 | if (exchangeXmlAnswer.length) {
|
402 | exchangeXmlAnswer = exchangeXmlAnswer.join('');
|
403 |
|
404 |
|
405 | processData(exchangeXmlAnswer);
|
406 | }
|
407 | self.completed({
|
408 | data: users || null,
|
409 | total: users ? users.length : 0,
|
410 | success: !error,
|
411 | error: error
|
412 | });
|
413 | });
|
414 | }
|
415 |
|
416 |
|
417 | if (self.exchangeAuthType=='ntlm') {
|
418 | this.getNtlmRequest(
|
419 | [
|
420 | servicesUrl,
|
421 | '-H',
|
422 | 'Content-Type: text/xml',
|
423 | '--ntlm',
|
424 | '-f',
|
425 | '-s',
|
426 | '-S',
|
427 | '--user',
|
428 | login + ':' + password,
|
429 | '-d',
|
430 | query
|
431 | ],
|
432 | processNtlmResponse
|
433 | );
|
434 | } else {
|
435 | options.auth = login + ":" + password;
|
436 | var req = this.getBasicRequest(options, processBasicResponse);
|
437 | req.on('error', function(e) {
|
438 | console.error(e);
|
439 | });
|
440 | req.write(query);
|
441 | req.end();
|
442 | }
|
443 |
|
444 | } else {
|
445 |
|
446 | self.completed({
|
447 |
|
448 | });
|
449 |
|
450 | }
|
451 | }
|
452 | });
|