• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

telefonicaid / fiware-pep-steelskin / 11252568670

09 Oct 2024 09:53AM UTC coverage: 79.598% (-3.2%) from 82.787%
11252568670

Pull #544

github

web-flow
Merge e762d4942 into 2c45e28dd
Pull Request #544: Task/add access matches (about users, headers, path. .. ) to access Logger

266 of 371 branches covered (71.7%)

Branch coverage included in aggregate %.

19 of 54 new or added lines in 4 files covered. (35.19%)

24 existing lines in 2 files now uncovered.

764 of 923 relevant lines covered (82.77%)

50.21 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

59.12
/lib/middleware/proxy.js
1
/*
2
 * Copyright 2014 Telefonica Investigación y Desarrollo, S.A.U
3
 *
4
 * This file is part of fiware-pep-steelskin
5
 *
6
 * fiware-pep-steelskin is free software: you can redistribute it and/or
7
 * modify it under the terms of the GNU Affero General Public License as
8
 * published by the Free Software Foundation, either version 3 of the License,
9
 * or (at your option) any later version.
10
 *
11
 * fiware-pep-steelskin is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14
 * See the GNU Affero General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU Affero General Public
17
 * License along with fiware-pep-steelskin.
18
 * If not, seehttp://www.gnu.org/licenses/.
19
 *
20
 * For those usages not covered by the GNU Affero General Public License
21
 * please contact with::[iot_support@tid.es]
22
 */
23

24
'use strict';
25

26
var config = require('../../config'),
1✔
27
    errors = require('../errors'),
28
    request = require('request'),
29
    constants = require('../constants'),
30
    validationHeaders = [
31
        'fiware-service',
32
        'fiware-servicepath',
33
        'x-auth-token'
34
    ],
35
    authorizationHeaders = [
36
        'x-auth-token'
37
    ],
38
    winston = require('winston'),
39
    logger = require('logops'),
40
    configAccessMatch = require('../../configAccessMatch.js').configAccessMatch,
41
    accessLogger;
42

43
const fs = require('fs');
1✔
44
const configAccessMatchFilePath = './configAccessMatch.js';
1✔
45

46
function requireUncached(module) {
1✔
NEW
47
    delete require.cache[require.resolve(module)];
×
NEW
48
    return require(module);
×
49
}
50

51
function watchConfigAccessMatchFile() {
1✔
52
    fs.watch(configAccessMatchFilePath, (event, filename) => {
143✔
NEW
53
        logger.info('watchConfigAccessMatchFile changed by %s detected in file %s', event, filename);
×
NEW
54
        try {
×
NEW
55
            configAccessMatch = requireUncached('../../configAccessMatch.js').configAccessMatch;
×
NEW
56
            logger.debug('reloaded configAccessMatch %j', configAccessMatch);
×
57
        } catch (err) {
NEW
58
            logger.error('Error %s reloading module: %s ', err, filename);
×
59
        }
60
    });
61
}
62

63
/**
64
 * Middleware to extract the organization data from the request.
65
 *
66
 * @param {Object} req           Incoming request.
67
 * @param {Object} res           Outgoing response.
68
 * @param {Function} next        Call to the next middleware in the chain.
69
 */
70
function extractOrganization(req, res, next) {
1✔
71
    const logger = req.logger;
152✔
72
    if (req.headers[constants.ORGANIZATION_HEADER]) {
152!
73
        req.organization = req.headers[constants.ORGANIZATION_HEADER];
152✔
74
        req.service = req.headers[constants.ORGANIZATION_HEADER];
152✔
75
        req.subService = req.headers[constants.PATH_HEADER];
152✔
76
        next();
152✔
77
    } else {
78
        logger.error('[PROXY-GEN-001] Organization headers not found');
×
79
        next(new errors.OrganizationNotFound());
×
80
    }
81
}
82

83
/**
84
 * Middleware to extract the user data (usually a token) from the request.
85
 *
86
 * @param {Object} req           Incoming request.
87
 * @param {Object} res           Outgoing response.
88
 * @param {Function} next        Call to the next middleware in the chain.
89
 */
90
function extractUserId(req, res, next) {
1✔
91
    const logger = req.logger;
152✔
92
    if (req.headers[constants.AUTHORIZATION_HEADER]) {
152!
93
        req.userId = req.headers[constants.AUTHORIZATION_HEADER];
152✔
94
        next();
152✔
95
    } else {
96
        logger.error('[PROXY-GEN-002] User ID headers not found');
×
97
        next(new errors.UserNotFound());
×
98
    }
99
}
100

101
/**
102
 * Generates the FRN to identify the target resource of the request. The FRN is composed of information from the
103
 * service and subservice headers and some configuration information.
104
 *
105
 * @param {Object} req           Incoming request.
106
 * @param {Object} res           Outgoing response.
107
 * @param {Function} next        Invokes the next middleware in the chain.
108
 */
109
function generateFRN(req, res, next) {
1✔
110
    const logger = req.logger;
153✔
111
    var frn = config.resourceNamePrefix + config.componentName + ':';
153✔
112

113
    if (req.organization) {
153✔
114
        frn += req.organization + ':';
151✔
115
    } else {
116
        frn += ':';
2✔
117
    }
118

119
    if (req.headers[constants.PATH_HEADER]) {
153✔
120
        frn += req.headers[constants.PATH_HEADER] + ':';
152✔
121
    } else {
122
        frn += ':';
1✔
123
    }
124

125
    if (req.resourceUrl) {
153✔
126
        frn += req.resourceUrl + ':';
14✔
127
    } else {
128
        frn += ':';
139✔
129
    }
130

131
    if (req.entityType) {
153!
132
        frn += req.entityType;
×
133
    } else {
134
        frn += ':';
153✔
135
    }
136
    logger.debug('Generated FRN: ', frn);
153✔
137
    req.frn = frn;
153✔
138

139
    next();
153✔
140
}
141

142
/**
143
 * Redirects the incoming request to the proxied host.
144
 * read to guess its type.
145
 *
146
 * @param {Object} req           Incoming request.
147
 * @param {Object} res           Outgoing response.
148
 * @param {Function} next        Invokes the next middleware in the chain.
149
 */
150
function sendRequest(req, res, next) {
1✔
151
    const logger = req.logger;
78✔
152
    var options = {
78✔
153
        uri: 'http://' + config.resource.original.host + ':' + config.resource.original.port + req.path,
154
        qs: req.query,
155
        method: req.method,
156
        headers: req.headers
157
    };
158

159
    if (!options.headers[constants.X_FORWARDED_FOR_HEADER] && req.connection.remoteAddress) {
78✔
160
        options.headers[constants.X_FORWARDED_FOR_HEADER] = req.connection.remoteAddress;
75✔
161
    }
162
    if (!options.headers[constants.CORRELATOR_HEADER] && req.corr) {
78✔
163
        options.headers[constants.CORRELATOR_HEADER] = req.corr;
5✔
164
    }
165

166
    if (req.is('*/json')) {
78✔
167
        options.body = JSON.stringify(req.body);
77✔
168
    } else {
169
        options.body = req.rawBody;
1✔
170
    }
171

172
    delete options.headers['content-length'];
78✔
173
    options.headers.connection = 'close';
78✔
174

175
    res.oldWriteHead = res.writeHead;
78✔
176
    res.writeHead = function(statusCode, reasonPhrase, headers) {
78✔
177
        if (res._headers['transfer-encoding']) {
78!
178
            delete res._headers['transfer-encoding'];
×
179
        }
180

181
        res.oldWriteHead(statusCode, reasonPhrase, headers);
78✔
182
    };
183

184
    logger.debug('Forwarding request:\n\n%j\n', options);
78✔
185

186
    req.fwdResponse = request(options).on('error', function handleConnectionError(e) {
78✔
187
        logger.error('Error forwarding the request to target proxy: %s', e.message);
1✔
188

189
        if (config.dieOnRedirectError) {
1!
190
            logger.fatal('[PROXY-FATAL-001] Configured to die upon error in a redirection. Stopping process.');
×
191

192
            process.exit(-1);
×
193
        } else {
194
            next(new errors.TargetServerError(e.message));
1✔
195
        }
196
    });
197
    next();
78✔
198
}
199

200
/**
201
 * Check here MATCH file patterns
202
 *
203
 * @param {Object} req           Incoming request.
204
 * @param {String} accessMsg     Incoming accessMsg
205
 * @return {String}              String message corresponding with accessMsg and found matches
206
 */
207
function checkAccessMatches(req, accessMsg) {
1✔
NEW
208
    if (req.userName in configAccessMatch.users ) {
×
NEW
209
        accessMsg += ' MATCHED USER ' + req.userName;
×
210
    }
NEW
211
    for (var header of configAccessMatch.headers) {
×
NEW
212
        if (Object.keys(header).includes('fiware-service')) {
×
NEW
213
            if (req.service.includes(header['fiware-service'])) {
×
NEW
214
                accessMsg += ' MATCHED HEADER Service ' + header['fiware-service'];
×
215
            }
NEW
216
        } else if (Object.keys(header).includes('fiware-servicepath')) {
×
NEW
217
            if (req.subService.includes(header['fiware-servicepath'])) {
×
NEW
218
                accessMsg += ' MATCHED HEADER SubService ' + header['fiware-servicepath'];
×
219
            }
NEW
220
        } else if (Object.keys(header).includes('x-real-ip')) {
×
NEW
221
            if (req.connection.remoteAddress.includes(header['x-real-ip'])) {
×
NEW
222
                accessMsg += ' MATCHED HEADER Origin ' + header['x-real-ip'];
×
223
            }
224
        // } else if (Object.keys(header).includes('x-forwarded-for')) {
225
        //     if (req.headers.includes(header['x-forwarded-for'])) {
226
        //         accessMsg += ' MATCHED HEADER x-forwarded-for ' + header['x-forwarded-for'];
227
        //     }
228
        }
229
    }
NEW
230
    for (var subpath of configAccessMatch.subpaths) {
×
NEW
231
        if (req.path.includes(subpath)) {
×
NEW
232
            accessMsg += ' MATCHED SUBPATH ' + subpath;
×
233
        }
234
    }
NEW
235
    for (var subquery of configAccessMatch.subqueries) {
×
NEW
236
        if (JSON.stringify(req.query).includes(subquery)) {
×
NEW
237
            accessMsg += ' MATCHED SUBQUERY ' + subquery;
×
238
        }
239
    }
NEW
UNCOV
240
    for (var text of configAccessMatch.body) {
×
NEW
UNCOV
241
        if (JSON.stringify(req.body).includes(text)) {
×
NEW
UNCOV
242
            accessMsg += ' MATCHED BODY ' + text;
×
243
        }
244
    }
NEW
UNCOV
245
    return accessMsg;
×
246
}
247

248

249
/**
250
 * Account Log
251
 * read to guess its type.
252
 *
253
 * @param {Object} req           Incoming request.
254
 * @param {Object} res           Outgoing response.
255
 * @param {Function} next        Invokes the next middleware in the chain.
256
 */
257
function accountInfo(req, res, next) {
1✔
UNCOV
258
    if ('fwdResponse' in req) {
×
UNCOV
259
        if (!accessLogger) {
×
UNCOV
260
            accessLogger = new winston.Logger({
×
261
                level: 'info',
262
                transports: [
263
                    new(winston.transports.File)({
264
                        filename: config.access.accountFile
265
                    })
266
                ]
267
            });
268
        }
269
        req.fwdResponse = req.fwdResponse.on('response', function(res) {
×
NEW
270
            var accessMsg = 'Right Attempt';
×
NEW
271
            accessMsg = checkAccessMatches(req, accessMsg);
×
NEW
272
            accessLogger.info(accessMsg +
×
273
                              ' | ResponseStatus=' + req.fwdResponse.response.statusCode +
274
                              ' | Token=' + req.headers['x-auth-token'] +
275
                              ' | Origin=' + req.connection.remoteAddress +
276
                              ' | UserId=' + req.userId +
277
                              ' | UserName=' + req.userName +
278
                              ' | ServiceId=' + req.serviceId +
279
                              ' | Service=' + req.service +
280
                              ' | SubServiceId=' + req.subserviceId +
281
                              ' | SubService=' + req.subService +
282
                              ' | Action=' + req.action +
283
                              ' | Path=' + req.path +
284
                              ' | Query=' + JSON.stringify(req.query) +
285
                              ' | Body=' + JSON.stringify(req.body).slice(0, 100) + // not all body
286
                              ' | Date=' + new Date().toJSON());
287
        });
288
    }
289
    next();
×
290
}
291

292
/**
293
 * Invoque request and get response. The request is not read with a pipe, as it has been completely
294
 * read to guess its type.
295
 *
296
 * @param {Object} req           Incoming request.
297
 * @param {Object} res           Outgoing response.
298
 * @param {Function} next        Invokes the next middleware in the chain.
299
 */
300
function sendResponse(req, res, next) {
1✔
301
    req.fwdResponse.pipe(res);
78✔
302
}
303

304
/**
305
 * Generates a middleware that checks for the pressence of the mandatory headers passed as a parameter, returning a
306
 * MISSING_HEADERS error if any one is not found.
307
 *
308
 * @param {Array} mandatoryHeaders      List of headers to check.
309
 * @return {Function}                  An express middleware that checks for the presence of the headers.
310
 */
311
function checkMandatoryHeaders(mandatoryHeaders) {
1✔
312
    return function(req, res, next) {
2✔
313
        var missing = [];
165✔
314

315
        for (var i = 0; i < mandatoryHeaders.length; i++) {
165✔
316
            if (!req.headers[mandatoryHeaders[i]] || req.headers[mandatoryHeaders[i]].trim() === '') {
489✔
317
                missing.push(mandatoryHeaders[i]);
15✔
318
            }
319
        }
320

321
        if (missing.length !== 0) {
165✔
322
            next(new errors.MissingHeaders(JSON.stringify(missing)));
11✔
323
        } else {
324
            next();
154✔
325
        }
326
    };
327
}
328

329
exports.generateFRN = generateFRN;
1✔
330
exports.extractUserId = extractUserId;
1✔
331
exports.extractOrganization = extractOrganization;
1✔
332
exports.sendRequest = sendRequest;
1✔
333
exports.sendResponse = sendResponse;
1✔
334
exports.accountInfo = accountInfo;
1✔
335
exports.checkMandatoryHeaders = checkMandatoryHeaders(validationHeaders);
1✔
336
exports.checkAuthorizationHeader = checkMandatoryHeaders(authorizationHeaders);
1✔
337
exports.watchConfigAccessMatchFile = watchConfigAccessMatchFile;
1✔
338
exports.checkAccessMatches = checkAccessMatches;
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc