UNPKG

swagger-tools

Version:

Various tools for using and integrating with Swagger.

179 lines (152 loc) 6.86 kB
/* * The MIT License (MIT) * * Copyright (c) 2014 Apigee Corporation * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ 'use strict'; var _ = require('lodash-compat'); var async = require('async'); var debug = require('debug')('swagger-tools:middleware:security'); var helpers = require('./helpers'); var getScopeOrAPIKey = function (req, secDef, secName, secReq) { var swaggerVersion = req.swagger.swaggerVersion; var apiKeyPropName = swaggerVersion === '1.2' ? secDef.keyname : secDef.name; var apiKeyLocation = swaggerVersion === '1.2' ? secDef.passAs : secDef.in; var scopeOrKey; if (secDef.type === 'oauth2') { if (swaggerVersion === '1.2') { scopeOrKey = _.map(secReq[secName], function (scope) { return scope.scope; }); } else { scopeOrKey = secReq[secName]; } } else if (secDef.type === 'apiKey') { if (apiKeyLocation === 'query') { scopeOrKey = (req.query ? req.query : helpers.parseQueryString(req))[apiKeyPropName]; } else if (apiKeyLocation === 'header') { scopeOrKey = req.headers[apiKeyPropName.toLowerCase()]; } } return scopeOrKey; }; var sendSecurityError = function (err, res, next) { // Populate default values if not present if (!err.code) { err.code = 'server_error'; } if (!err.statusCode) { err.statusCode = 403; } if (err.headers) { _.each(err.headers, function (header, name) { res.setHeader(name, header); }); } res.statusCode = err.statusCode; next(err); }; /** * Middleware for using Swagger security information to authenticate requests. * * This middleware also requires that you use the swagger-metadata middleware before this middleware. It is recommended * that this middleware is included before swagger-validator and swagger-router. This makes no attempt to work around * invalid Swagger documents. * * * A SecurityImplementation is essentially middleware must include 2 exported methods: * configure (SecurityDefinition) * authorize (request, response, SecurityRequirement) * * @param {object} [options] - The middleware options * [options.{name}={handler}] - the keys match SecurityDefinition names and the associated values are * functions that accept the following parameters: (request, * securityDefinition, scopes, callback) where callback accepts one * argument - an Error if unauthorized. The Error may include "message", * "state", and "code" fields to be conveyed to the client in the response * body and a "headers" field containing an object representing headers * to be set on the response to the client. In addition, if the Error has * a statusCode field, the response statusCode will be set to match - * otherwise, the statusCode will be set to 403. * * @returns the middleware function */ exports = module.exports = function (options) { var handlers = options || {}; debug('Initializing swagger-security middleware'); debug(' Security handlers:%s', Object.keys(handlers).length > 0 ? '' : ' ' + Object.keys(handlers).length); _.each(options, function (func, name) { debug(' %s', name); }); return function swaggerSecurity (req, res, next) { var operation = req.swagger ? req.swagger.operation : undefined; var securityReqs; debug('%s %s', req.method, req.url); debug(' Will process: %s', _.isUndefined(operation) ? 'no' : 'yes'); if (operation) { securityReqs = req.swagger.swaggerVersion === '1.2' ? // Global (path level), authorization support is not possible: // Not possible due to https://github.com/swagger-api/swagger-spec/issues/159 _.reduce(req.swagger.operation.authorizations, function (arr, authorization, name) { var obj = {}; obj[name] = _.map(authorization, function (scope) { return scope.scope; }); return arr.concat(obj); }, []) : req.swagger.operation.security || req.swagger.swaggerObject.security; if (securityReqs && securityReqs.length > 0) { async.map(securityReqs, function (secReq, cb) { // logical OR - any one can allow var secName; async.map(Object.keys(secReq), function (name, cb) { // logical AND - all must allow var secDef = req.swagger.swaggerVersion === '1.2' ? req.swagger.resourceListing.authorizations[name] : req.swagger.swaggerObject.securityDefinitions[name]; var handler = handlers[name]; secName = name; if (!handler) { return cb(new Error('unknown security handler: ' + name)); } return handler(req, secDef, getScopeOrAPIKey(req, secDef, name, secReq), cb); }, function (err) { debug(' Security check (%s): %s', secName, _.isNull(err) ? 'allowed' : 'denied'); // swap normal err and result to short-circuit the logical OR if (err) { return cb(undefined, err); } return cb(new Error('OK')); }); }, function (ok, errors) { // note swapped results var allowed = !_.isNull(ok) && ok.message === 'OK'; debug(' Request allowed: %s', allowed); if (allowed) { return next(); } return sendSecurityError(errors[0], res, next); }); } else { return next(); } } else { return next(); } }; };