-
Notifications
You must be signed in to change notification settings - Fork 1
/
integration.js
206 lines (174 loc) · 5.3 KB
/
integration.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
'use strict';
const request = require('postman-request');
const config = require('./config/config');
const async = require('async');
const fs = require('fs');
let Logger;
let requestWithDefaults;
const MAX_PARALLEL_LOOKUPS = 10;
/**
*
* @param entities
* @param options
* @param cb
*/
function startup(logger) {
let defaults = {};
Logger = logger;
const { cert, key, passphrase, ca, proxy, rejectUnauthorized } = config.request;
if (typeof cert === 'string' && cert.length > 0) {
defaults.cert = fs.readFileSync(cert);
}
if (typeof key === 'string' && key.length > 0) {
defaults.key = fs.readFileSync(key);
}
if (typeof passphrase === 'string' && passphrase.length > 0) {
defaults.passphrase = passphrase;
}
if (typeof ca === 'string' && ca.length > 0) {
defaults.ca = fs.readFileSync(ca);
}
if (typeof proxy === 'string' && proxy.length > 0) {
defaults.proxy = proxy;
}
if (typeof rejectUnauthorized === 'boolean') {
defaults.rejectUnauthorized = rejectUnauthorized;
}
requestWithDefaults = request.defaults(defaults);
}
function doLookup(entities, options, cb) {
let lookupResults = [];
let tasks = [];
Logger.debug(entities);
entities.forEach((entity) => {
let requestOptions = {
method: 'POST',
uri: `${options.url}/?api`,
form: {
apikey: options.apiKey
},
json: true
};
if (entity.types.includes('cve')) {
requestOptions.form.advancedsearch = `cve:${entity.value}`;
} else {
requestOptions.form.search = entity.value;
}
Logger.trace({ uri: requestOptions }, 'Request URI');
tasks.push(function (done) {
requestWithDefaults(requestOptions, function (httpError, res, body) {
if (httpError) {
return done({
detail: 'HTTP Request Error',
error: httpError
});
}
let result = {};
// The VulDB API is unique in that the HTTP status code returned by the server is always 200. Instead
// There is a custom status code attached to the `body.response.status` property (which in theory should
// always be there except when the HTTP status Code is not 200
let statusCode = 500;
if (body && body.response && body.response.status) {
// the status code is a string so we convert to an integer
statusCode = +body.response.status;
}
Logger.trace(requestOptions);
Logger.trace({ body, statusCode }, 'Result of Lookup');
if (statusCode === 200) {
result = {
entity,
body
};
} else if (statusCode === 404 || statusCode === 202 || statusCode === 204) {
result = {
entity,
body: null
};
} else {
let error = {
err: 'Unknown Error',
body,
detail: 'Integration encountered an unexpected response'
};
if (statusCode === 401) {
error = {
err: 'Unauthorized',
detail: 'Authentication required, API key missing or unrecognized'
};
} else if (statusCode === 403) {
error = {
err: 'API rate exceeded',
detail: 'API rate exceeded, no further requests allowed until counter reset'
};
} else if (statusCode === 405) {
error = {
err: 'Unknown request type',
detail: 'Unknown request type'
};
} else if (Math.round(statusCode / 10) * 10 === 500) {
error = {
err: 'Server Error',
detail: 'Unexpected Server Error',
body
};
}
return done(error);
}
done(null, result);
});
});
});
async.parallelLimit(tasks, MAX_PARALLEL_LOOKUPS, (err, results) => {
if (err) {
Logger.error({ err: err }, 'Error');
cb(err);
return;
}
results.forEach((result) => {
if (
result.body === null ||
!result.body.result ||
(result.body && Array.isArray(result.body.result) && result.body.result.length === 0)
) {
// body.result is an array of result items. If it is empty or does not exist then there are no results
// for this lookup
lookupResults.push({
entity: result.entity,
data: null
});
} else {
lookupResults.push({
entity: result.entity,
data: {
summary: [`Results: ${result.body.result.length}`],
details: result.body
}
});
}
});
Logger.debug({ lookupResults }, 'Results');
cb(null, lookupResults);
});
}
function validateStringOption(errors, options, optionName, errMessage) {
if (
typeof options[optionName].value !== 'string' ||
(typeof options[optionName].value === 'string' && options[optionName].value.length === 0)
) {
errors.push({
key: optionName,
message: errMessage
});
}
}
function validateOptions(options, callback) {
let errors = [];
validateStringOption(errors, options, 'url', 'You must provide a valid URL');
validateStringOption(errors, options, 'apiKey', 'You must provide a valid API Key');
callback(null, errors);
}
module.exports = {
doLookup,
startup,
validateOptions
};