· 8 years ago · Mar 02, 2018, 08:16 AM
1var sh_WebServiceRESTMessageStorage = Class.create();
2sh_WebServiceRESTMessageStorage.prototype = {
3 /**
4 *
5 *
6 * @param {object} requestObj - request object from inbound post request
7 * @param {object} responseObj - response object from inbound post request
8 */
9 initialize: function (requestObj, responseObj, operation) {
10 if (requestObj != undefined) {
11 this.request = requestObj;
12 }
13 if (responseObj != undefined) {
14 this.response = responseObj;
15 }
16 this.operation = operation;
17 this.integration = '';
18 this.log = new sh_WebServiceLog();
19 this.queueRecord = new GlideRecord('u_ws_queue');
20 this.queueRecord.initialize();
21 this.queueRecord.update();
22 this.contentType = this.request.headers['content-type'];
23
24 this.log.setSource(this.queueRecord.u_number + '');
25 this.log.logDebug('Request Received');
26
27 this.genericUtils = new sh_WebServiceGenericUtils(this.log);
28
29 this.c_encodedQuery = '';
30 this.c_attribute = '';
31
32 },
33
34 /**
35 * Function that get all the info from the request,
36 * transfer that info into a a record in the u_ws_queue table
37 * and sends a response back to the requester
38 *
39 */
40 processRequest: function () {
41 var bodyString = this.getBodyString();
42 var body = this.getBody(bodyString);
43 var sender_identifier = this.getSenderIdentifier(body);
44
45 if (!sender_identifier) {
46 response = this.createJSONResponse('sender_identifier.not_found');
47 this.badRequestRecord(response, bodyString);
48 return;
49 }
50 this.log.logDebug('Sender Identifier: ' + sender_identifier);
51
52 var integration = this.getIntegration(body);
53
54 if (!this.verifyRecord(integration, 'integration', bodyString)) {
55 return;
56 }
57 this.log.logDebug('Integration: ' + integration.u_name);
58 this.integration = integration.sys_id + '';
59 var path = integration.u_data_path;
60
61 if (!this.getOperation(integration, bodyString)) {
62 return;
63 }
64
65 if (this.contentType != 'application/json') {
66 bodyString = JSON.stringify(body, null, 4);
67 }
68
69 var response;
70 var environment = this.genericUtils.getEnvironment(this.integration, sender_identifier);
71
72 if (!this.verifyRecord(environment, 'environment', bodyString, environment)) {
73 return;
74 }
75 this.log.setLevel(environment.u_client.u_level + '');
76 this.log.logDebug('environment: ' + environment.u_name);
77
78 var clientConfiguration;
79 var fields = this.genericUtils.getFieldsObject(body, path);
80
81 if (!fields) {
82 this.log.logDebug('Fields not found');
83 response = this.createJSONResponse('data.not_found');
84 this.badRequestRecord(response, bodyString, environment);
85 return;
86 }
87
88 var numberOfRecords = this.genericUtils.checkBulkData(fields);
89 var bulkData = numberOfRecords ? true : false;
90 var mod;
91
92 if (this.operation.u_operation_type == 'get') {
93 mod = this.request.pathParams.module + '';
94 clientConfiguration = this.genericUtils.retrieveGetConfiguration(this.integration, this.operation.u_name + '', environment.u_client, mod);
95
96 } else if (this.operation.u_operation_type == 'delete') {
97 mod = this.request.pathParams.module + '';
98 clientConfiguration = this.genericUtils.retrieveGetConfiguration(this.integration, this.operation.u_name + '', environment.u_client, mod);
99
100 } else {
101 clientConfiguration = this.genericUtils.getConfiguration(body, this.integration, this.operation.u_operation_type + '', environment.u_client, bulkData);
102 }
103
104 var configuration = clientConfiguration.u_web_service_configuration;
105
106 if (!this.verifyRecord(configuration, 'configuration', bodyString, environment, configuration)) {
107 return;
108 }
109 this.log.logDebug('configuration: ' + configuration.u_name);
110
111 var maxRecords = configuration.u_bulk_max_records ? configuration.u_bulk_max_records : gs.getProperty('service_connector.inbound_bulk.max_records');
112
113 if (bulkData && numberOfRecords > maxRecords) {
114 var response = this.createJSONResponse('max_records.exceed');
115 response.detail += maxRecords;
116 this.badRequestRecord(response, bodyString, environment, configuration);
117 return;
118 }
119
120 var record;
121
122 if (this.operation.u_operation_type + '' == 'post') {
123 if (bulkData) {
124 record = '';
125 } else {
126 record = this.getRecordByCoalesce(bodyString, environment, configuration, clientConfiguration, fields);
127 }
128 } else if (this.operation.u_operation_type + '' == 'delete') {
129 var sysID = this.request.pathParams.number + '';
130 record = this.getRecordBySysID(configuration, sysID, bodyString, environment);
131
132 } else {
133 record = this.getRecordByNumber(bodyString, environment, configuration, clientConfiguration);
134 }
135
136 if (record == undefined) {
137 return;
138 }
139
140 var responseBody;
141 var status = 'ready';
142 var maintenance = this.genericUtils.checkInboundMaintenanceMode(environment.u_client);
143
144 //creates a generic response message to give acknoledgment of the received message
145 if (integration.u_mode != 'synchronous' || configuration.u_external_processing) {
146 if (maintenance) {
147 responseBody = this.createJSONResponse('maintenance.async');
148 status = 'maintenance process';
149 } else {
150 responseBody = this.createJSONResponse('success');
151 }
152 }
153
154 //create a queue record with the information from the request
155 var queueProto = {
156 u_payload: this.requestToString(this.request, bodyString),
157 u_target_table: configuration.u_target + '',
158 u_target_record: record.sys_id ? record.sys_id + '' : '',
159 u_queue: 'inbound',
160 u_state: status,
161 u_type: 'rest',
162 u_data: bodyString,
163 u_operation: this.operation.u_operation_type + '',
164 u_response: JSON.stringify(responseBody),
165 u_environment: environment.sys_id + '',
166 u_configuration: configuration.sys_id + '',
167 u_integration: this.integration,
168 u_bulk_data: bulkData,
169 u_bulk_records: numberOfRecords
170
171 };
172
173 var queueGR = this.genericUtils.createQueueRecord(queueProto, this.queueRecord);
174
175 if (integration.u_mode == 'synchronous' && !configuration.u_external_processing) {
176 if (maintenance) {
177 responseBody = this.createJSONResponse('maintenance.sync');
178 queueGR.u_response = JSON.stringify(responseBody);
179 queueGR.u_state = 'maintenance rejected';
180 queueGR.update();
181 } else {
182 responseBody = new sh_WebServiceQueueProcessor().processInboundQueue(queueGR);
183
184 if (responseBody !== true) {
185 this.synchResponse(responseBody);
186 queueGR.u_response = JSON.stringify(responseBody, null, 4);
187 queueGR.update();
188 }
189 }
190 }
191 this.log.logDebug('Request processed');
192 },
193
194 /**
195 * Generates a serialized (JSON format) object based on the request and body of a
196 * REST request message
197 *
198 * @param {object} request
199 * @param {string} body
200 * @returns a serialized object containing the request and the body (JSON format)
201 */
202 requestToString: function (request, body) {
203 var restMessage = 'header: ' + JSON.stringify(request.headers) + '\n';
204 restMessage += 'body: ' + body + '\n';
205 restMessage += 'query: ' + request.queryString;
206
207 return restMessage;
208 },
209
210 /**
211 * Function that determines the right integration based on the message received
212 *
213 * @param {object} body
214 * @returns {GlideRecord}
215 */
216 getIntegration: function (body) {
217 var integration = '';
218 var module = '';
219
220 if (this.operation != 'get' && this.operation != 'delete') {
221 module = body.module + '';
222 } else {
223 module = this.request.pathParams.module + '';
224
225 }
226
227 var integrations = new GlideRecord('u_ws_integration');
228 integrations.addQuery('u_protocol', 'rest');
229 integrations.addQuery('u_identifier', module);
230 integrations.query();
231
232 if (integrations.next()) {
233 integration = integrations;
234 }
235
236 return integration;
237 },
238
239 /**
240 * Retrieves the sender identifier from the body or from the URL
241 * in case it's a GET request.
242 *
243 * @param body
244 * @returns {string}
245 */
246 getSenderIdentifier: function (body) {
247 if (this.operation != 'get' && this.operation != 'delete') {
248 return body.sender_identifier + '';
249 }
250 return this.request.pathParams.sender_identifier + '';
251 },
252
253 /**
254 * Gets the body from the request and returns it as a string.
255 * If the request is a GET request, then returns it from the
256 * URI.
257 *
258 * @returns {string}
259 */
260 getBodyString: function () {
261 if (this.operation != 'get' && this.operation != 'delete') {
262 try {
263 return this.request.body.dataString;
264 } catch (e) {
265 this.log.logError('Could not find body.');
266 }
267 }
268
269 this.log.logDebug('Request URL: ' + this.request.uri);
270 return this.request.uri;
271 },
272
273 /**
274 * Transforms the body string into an object.
275 * If the application type of the request is XML then
276 * it calls for another function to transform it.
277 *
278 * @param bodyString
279 * @returns {object}
280 */
281 getBody: function (bodyString) {
282 if (this.operation != 'get' && this.operation != 'delete') {
283 var body;
284 try {
285 if (this.contentType.includes('application/json')) {
286 this.log.logDebug('Getting body application/json');
287 body = JSON.parse(bodyString);
288 } else {
289 this.log.logDebug('Getting body application/xml');
290 body = this.genericUtils.xmlBodyToJson(bodyString);
291 }
292 } catch (err) {
293 this.log.logError(err.message + '');
294 response = this.createJSONResponse('not.acceptable');
295 this.badRequestRecord(response, bodyString);
296 return '';
297 }
298 return body;
299 }
300
301 return bodyString;
302 },
303
304 /**
305 * Verifies if a record is valid for processing. Accepts integration,
306 * environments or configuration records. If the record is not valid or
307 * non-existing, generates a code that is used to generate a Bad Request response
308 * with details.
309 * If the record is valid, returns the record itself.
310 *
311 * @param record
312 * @param type
313 * @param bodyString
314 * @param environment
315 * @param configuration
316 * @returns {GlideRecord}
317 */
318 verifyRecord: function (record, type, bodyString, environment, configuration) {
319 var code = '';
320
321 if (!record) {
322 switch (type) {
323 case 'integration':
324 code = 'integration.not_found';
325 break;
326 case 'environment':
327 code = 'environment.not_found';
328 break;
329 case 'configuration':
330 code = 'configuration.not_found';
331 break;
332 }
333 } else if (!record.u_active) {
334 switch (type) {
335 case 'integration':
336 code = 'integration.inactive';
337 break;
338 case 'environment':
339 code = 'credentials.inactive';
340 break;
341 case 'configuration':
342 code = 'configuration.inactive';
343 break;
344 }
345 } else if (record.code) {
346 code = record.code;
347 }
348
349 if (code != '') {
350 var response = this.createJSONResponse(code);
351 this.badRequestRecord(response, bodyString, environment, configuration);
352 return undefined;
353 }
354
355 return record;
356 },
357
358 /**
359 * Gets a record from a table using the record number. Only usable for searches
360 * in task table and task table extensions.
361 * If the record is not found or the external party is not allowed to access
362 * that record, a Bad Request response is generated containing the details.
363 *
364 * @param bodyString
365 * @param environment
366 * @param configuration
367 * @param clientConfiguration
368 * @returns {GlideRecord}
369 */
370 getRecordByNumber: function (bodyString, environment, configuration, clientConfiguration) {
371 this.log.logDebug('Getting record.');
372 var response;
373 var number = this.request.pathParams.number + '';
374 var record = this.targetExist(configuration.u_target, number);
375
376 if (!record) {
377 response = this.createJSONResponse('record.not_found');
378 this.badRequestRecord(response, bodyString, environment, configuration);
379 return undefined;
380 }
381
382 if (!GlideFilter.checkRecord(record, clientConfiguration.u_condition)) {
383 response = this.createJSONResponse('record.not_authorized');
384 this.badRequestRecord(response, bodyString, environment, configuration);
385 return undefined;
386 }
387
388 this.log.logDebug('Filter result' + GlideFilter.checkRecord(record, clientConfiguration.u_condition), 'EF');
389 return record;
390 },
391
392 /**
393 * Gets record by sys_id in the target table of the configuration record.
394 * Only used in the deletion process.
395 *
396 * @param {any} configuration
397 * @param {any} sysID
398 * @param {any} bodyString
399 * @param {any} environment
400 * @returns
401 */
402 getRecordBySysID: function (configuration, sysID, bodyString, environment) {
403 this.log.logDebug('Getting record by sysID - ' + sysID);
404 var hasRecord, response;
405 var record = new GlideRecord(configuration.u_target);
406
407 try {
408 hasRecord = record.get(sysID);
409 } catch (e) {
410 this.log.logError('An error occured while trying to find a record by by sys_id.');
411 }
412
413 if (!hasRecord) {
414 this.log.logError('Could not find record by sys_id.');
415 response = this.createJSONResponse('record.not_found');
416 this.badRequestRecord(response, bodyString, environment, configuration);
417 return undefined;
418 }
419 this.log.logDebug('Record was found.');
420 return record;
421 },
422
423 /**
424 * Gets a record from a table using a set of coalesce fields.
425 * If the record is not found or if the record is found but we are
426 * processing a POST request, then a Bad Request response will be
427 * generated.
428 *
429 * @param bodyString
430 * @param environment
431 * @param configuration
432 * @param clientConfiguration
433 * @param fields
434 * @returns {GlideRecord}
435 */
436 getRecordByCoalesce: function (bodyString, environment, configuration, clientConfiguration, fields) {
437 var coalesce = this.genericUtils.getCoalesceFields(configuration);
438 var response;
439 if (coalesce.hasNext()) {
440 var record = this.genericUtils.getCoalesceRecord(configuration.u_target, coalesce, fields);
441 if (record == '') {
442 return '';
443 } else if (typeof record == 'string') {
444 response = this.createJSONResponse('coalesce.not_found');
445 this.badRequestRecord(response, bodyString, environment, configuration);
446 return undefined;
447 } else {
448 this.log.logDebug('Coalesce record found with POST method');
449 response = this.createJSONResponse('record.exists');
450 this.badRequestRecord(response, bodyString, environment, configuration);
451 return undefined;
452 }
453 return record;
454 }
455 this.log.logDebug('Configuration without coalesce fields');
456 return '';
457 //no coalesce fields
458 },
459
460 /**
461 * Generates a JSON response based on a code passed as an argument.
462 * It searches in the response table for the record with the passed in
463 * code and uses it to generate a response.
464 * If the response code is 'not.acceptable' then a response is generated
465 * with the expected format for incoming requests.
466 *
467 * @param statusCode
468 * @returns {object}
469 */
470 createJSONResponse: function (statusCode) {
471 var gr = new GlideRecord('u_ws_response');
472 gr.addQuery('u_code', statusCode).addOrCondition('u_status_code', statusCode);
473 gr.query();
474 if (gr.next()) {
475 this.response.setContentType(this.contentType);
476 this.response.setStatus(gr.u_status_code);
477 var responseBody;
478 this.log.logDebug(gr.u_message + ': ' + gr.u_detail);
479
480 if (statusCode != 'not.acceptable') {
481 responseBody = {
482 "message": gr.u_message.toString(),
483 "detail": gr.u_detail.toString()
484 };
485 } else {
486 var format = {
487 "task": "task_name",
488 "fields": {
489 "field_name": "value",
490 "field2_name": "value"
491 }
492 };
493
494 responseBody = {
495 "message": gr.u_message.toString(),
496 "detail": gr.u_detail.toString(),
497 "right_JSON_format": format
498 };
499 }
500
501 this.response.setBody(responseBody);
502 return responseBody;
503 } else {
504 this.log.logError('Could not find response message');
505 }
506
507 },
508
509 /**
510 * Populates the global response object with the response body
511 * previously created, the content type and status code.
512 *
513 * @param responseBody
514 */
515 synchResponse: function (responseBody) {
516 this.log.logDebug('Sync response');
517 this.response.setContentType(this.contentType);
518 this.response.setStatus(responseBody.code);
519 this.response.setBody(responseBody.body);
520
521 },
522
523 /**
524 * Verifies if a record exists based on a number.
525 * To be deprecated.
526 *
527 * @param table
528 * @param number
529 * @returns {*}
530 */
531 targetExist: function (table, number) {
532 var target = new GlideRecord(table + '');
533 target.addQuery('number', number + '');
534 target.query();
535
536 return target.next() ? target : false;
537 },
538
539 /**
540 * Returns the operation record based on the processing operation
541 * and the integration.
542 *
543 * @param integration
544 * @param bodyString
545 * @returns {boolean}
546 */
547 getOperation: function (integration, bodyString) {
548 var op = new GlideRecord('u_ws_operation');
549 op.addQuery('u_direction', 'inbound');
550 op.addQuery('u_integration', integration.sys_id);
551 op.addQuery('u_operation_type', this.operation);
552 op.query();
553
554 if (op.next()) {
555 this.operation = op;
556 return true;
557 } else {
558 this.log.logDebug('Operation not found!');
559 var response = this.createJSONResponse('operation.not_found');
560 this.badRequestRecord(response, bodyString);
561 return false;
562 }
563 },
564
565 /**
566 * Generates a Bad Request Queue record with the most possible
567 * amount of information about the inbound request.
568 *
569 * @param response
570 * @param request
571 * @param environment
572 * @param configuration
573 */
574 badRequestRecord: function (response, request, environment, configuration) {
575 var queueProto = {
576 u_payload: this.requestToString(this.request, request),
577 u_target_table: configuration.u_target + '',
578 u_queue: 'inbound',
579 u_state: 'bad request',
580 u_type: 'rest',
581 u_operation: this.operation.u_operation_type ? this.operation.u_operation_type : this.operation,
582 u_environment: environment.sys_id + '',
583 u_configuration: configuration.sys_id + '',
584 u_integration: this.integration,
585 u_response: JSON.stringify(response)
586 };
587
588 var queueGR = this.genericUtils.createQueueRecord(queueProto, this.queueRecord);
589 },
590
591 /**
592 * DEPRECATED
593 * Captures a delete request and stores information about the request in the Queue message
594 * table.
595 * Processes the request and sends a 403 response back.
596 *
597 */
598 captureDELETErequest: function () {
599 this.c_encodedQuery = 'u_web_service_configuration.u_active=true^u_web_service_client.u_active=true^u_web_service_client.u_integration_user=' + gs.getUserID();
600 this.c_attribute = 'u_web_service_client';
601 var verifyClientConfiguration = this.genericUtils.glideGeneric('u_m2m_client_configuration', this.c_encodedQuery + '', this.c_attribute + '');
602 if (!verifyClientConfiguration) {
603 this.log.logDebug('request error');
604 this.createJSONResponse(403);
605 return;
606 }
607
608 //creates a generic response message to give acknoledgment of the received message
609 var responseBody = this.createJSONResponse(405);
610
611 //create a queue record with the information from the request
612 var queueProto = {
613 "u_payload": this.requestToString(this.request, ''),
614 "u_queue": 'inbound',
615 "u_state": 'processed',
616 "u_type": 'rest',
617 "u_data": '',
618 "u_operation": 'delete',
619 "u_client": verifyClientConfiguration + '',
620 "u_response": JSON.stringify(responseBody)
621 };
622
623 this.genericUtil.createQueueRecord(queueProto, this.queueRecord);
624 this.log.logDebug('delete processed');
625 },
626
627 type: 'sh_WebServiceRESTMessageStorage'
628};