Skip to content
This repository has been archived by the owner on Jan 31, 2025. It is now read-only.

Commit

Permalink
Merge pull request #68 from subeeshcbabu/security
Browse files Browse the repository at this point in the history
options.security to define the security authorize handlers directory
  • Loading branch information
shaunwarman authored Jul 18, 2016
2 parents 910c390 + b058173 commit c86b073
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 34 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
### Unreleased

- `options.security` to define the security authorize handlers directory.

### 1.0.8

- Added support for allowEmptyValue.
Expand Down
73 changes: 69 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ var builder = require('swaggerize-routes');

var routes = builder({
api: require('./api.json'),
handlers: './handlers'
handlers: './handlers',
security: './security' //Optional - security authorize handlers as per `securityDefinitions`
}));
```

Expand All @@ -31,6 +32,7 @@ Options:
- `handlers` - either a directory structure for route handlers or a premade object (see *Handlers Object* below).
- `basedir` - base directory to search for `handlers` path (defaults to `dirname` of caller).
- `schemas` - an array of `{name: string, schema: string|object}` representing additional schemas to add to validation.
- `security` - directory to scan for authorize handlers corresponding to `securityDefinitions`.

**Returns:** An array of the processed routes.

Expand Down Expand Up @@ -140,7 +142,7 @@ In the case where a different `x-handler` file is specified for each operation.

The directory generation will yield this object, but it can be provided directly as `options.handlers`.

Note that if you are programatically constructing a handlers obj this way, you must namespace HTTP verbs with `$` to
Note that if you are programmatically constructing a handlers obj this way, you must namespace HTTP verbs with `$` to
avoid conflicts with path names. These keys should also be *lowercase*.

Example:
Expand Down Expand Up @@ -168,7 +170,7 @@ The `routes` array returned from the call to the builder will contain `route` ob
- `name` - same as `operationId` in `api` definition.
- `description` - same as `description` in `path` for `api` definition.
- `method` - same as `method` from `api` `operation` definition.
- `security` - the security defintion for this route, either pulled from the operation level or path level.
- `security` - the security definition for this route, either pulled from the operation level or path level.
- `validators` - an array of validation objects created from each `parameter` on the `operation`.
- `handler` - a handler function appropriate to the target framework (e.g express).
- `consumes` - same as `consumes` in `api` definition.
Expand All @@ -182,11 +184,74 @@ The validator object in the `validators` array will have the following propertie
- `validate(value, callback)` - a function for validating the input data against the `parameter` definition.
- `schema` - the `joi` schema being validated against.

### Security directory

The `options.security` option specifies a directory to scan for security authorize handlers. These authorize handlers are bound to the api `securityDefinitions` defined in the swagger document.

The name of the `securityDefinitions` should match the file name of the authorize handler.

For example, for the security definition :

```json
"securityDefinitions": {
"default": {
"type": "oauth2",
"scopes": {
"read": "read pets.",
"write": "write pets."
}
},
"secondary": {
"type": "oauth2",
"scopes": {
"read": "read secondary pets.",
"write": "write secondary pets."
}
}
}
```

The `options.security`, say `security` directory should have following files:

```
├── security
   ├── default.js
   ├── secondary.js
```

### Schema Extension for security authorize handler

An alternative approach to `options.security` option, is use swagger schema extension (^x-) and define `x-authorize` as part of the `securityDefinitions`.

```json
"securityDefinitions": {
"default": {
"type": "oauth2",
"scopes": {
"read": "read pets.",
"write": "write pets."
},
"x-authorize": "security/default_authorize.js"
},
"secondary": {
"type": "oauth2",
"scopes": {
"read": "read secondary pets.",
"write": "write secondary pets."
},
"x-authorize": "security/secondary_authorize.js"
}
}
```

`x-authorize` will override any resolved authorize handlers defined by `options.security`.

### Security Object

The security object in the `route` is an object containing keys corresponding to names found under the [Swagger Security Definitions](https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md#securityDefinitionsObject).

Under each key will be an object containing the following properties:

- `scopes` - an array of scopes accepted for this route.
- `authorize` - a function that may be provided by defining a `x-authorize` attribute to the security definition.
- `authorize` - a function scanned from the authorize handlers defined by the `options.security` directory. Or this may be provided by defining a `x-authorize` attribute to the security definition.
32 changes: 20 additions & 12 deletions lib/buildroutes.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ function buildroutes(options) {
name: operation.operationId,
description: operation.description,
method: verb,
security: buildSecurity(options.basedir, api.securityDefinitions, operation.security || def.security),
security: buildSecurity(options, api.securityDefinitions, operation.security || def.security),
validators: [],
handler : undefined,
consumes: operation.consumes || api.consumes,
Expand Down Expand Up @@ -113,26 +113,34 @@ function matchpath(method, pathnames, handlers) {
* @param routeSecurity the security defined on this route.
* @returns {*}
*/
function buildSecurity(basedir, securityDefinitions, routeSecurity) {
function buildSecurity(options, securityDefinitions, routeSecurity) {
var security = {};
var basedir = options.basedir;

if (!securityDefinitions || !routeSecurity || !thing.isArray(routeSecurity)) {
return undefined;
}

routeSecurity.forEach(function (definition) {
Object.keys(definition).forEach(function (type) {
assert.ok(securityDefinitions[type], 'Unrecognized security definition (' + type + ')');
Object.keys(definition).forEach(function (defName) {
assert.ok(securityDefinitions[defName], 'Unrecognized security definition (' + defName + ')');

security[type] = {};
security[type].scopes = definition[type];
security[defName] = {};
//The value of security scheme is a list of scope names required for the execution
security[defName].scopes = definition[defName];

security[type].scopes.forEach(function (scope) {
assert.ok(thing.isString(scope) && Object.keys(securityDefinitions[type].scopes).indexOf(scope) > -1, 'Unrecognized scope (' + scope + ').');
security[defName].scopes.forEach(function (scope) {
assert.ok(thing.isString(scope) && Object.keys(securityDefinitions[defName].scopes).indexOf(scope) > -1, 'Unrecognized scope (' + scope + ').');
});

if (securityDefinitions[type]['x-authorize']) {
security[type].authorize = resolve(basedir, securityDefinitions[type]['x-authorize']);
if (options.security) {
//Security options found
//Resolve the security authorize handler path - basedir + options.security + securityDefinition name
security[defName].authorize = resolve(basedir, path.join(options.security, defName + '.js'));
}
//'x-authorize' can override the 'security' options and default handlers.
if (securityDefinitions[defName]['x-authorize']) {
security[defName].authorize = resolve(basedir, securityDefinitions[defName]['x-authorize']);
}
});
});
Expand All @@ -151,8 +159,8 @@ function resolve(basedir, pathname, method) {
var handler;
try {
//If the pathname is already a resolved function, return it.
//In the case of x-handler and x-authorize, users can define
//external handler/authorize modules and functions OR override
//In the case of x-handler and x-authorize, users can define
//external handler/authorize modules and functions OR override
//existing x-authorize functions.
if (thing.isFunction(pathname)) {
return pathname;
Expand Down
15 changes: 10 additions & 5 deletions test/fixtures/defs/pets.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@
"security": [
{
"default": ["read"]
},
{
"secondary": ["read"]
}
],
"jsonp": "callback",
Expand Down Expand Up @@ -95,6 +98,9 @@
"security": [
{
"default": ["write"]
},
{
"secondary": ["read"]
}
],
"parameters": [
Expand Down Expand Up @@ -298,11 +304,10 @@
},
"secondary": {
"type": "oauth2",
"scopes": [
"read",
"write"
],
"x-authorize": "extensions/authorize_secondary.js"
"scopes": {
"read": "read secondary pets.",
"write": "write secondary pets."
}
}
},
"definitions": {
Expand Down
5 changes: 5 additions & 0 deletions test/fixtures/extensions/default.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
'use strict';

module.exports = function authorize(req, res, next) {

};
5 changes: 5 additions & 0 deletions test/fixtures/extensions/secondary.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
'use strict';

module.exports = function authorize(req, res, next) {

};
33 changes: 20 additions & 13 deletions test/test-routebuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@ test('routebuilder', function (t) {
api = require('./fixtures/defs/pets.json');

t.test('build directory', function (t) {
routes = buildroutes({ api: api, basedir: path.join(__dirname, 'fixtures'), handlers: path.join(__dirname, 'fixtures/handlers')});
routes = buildroutes({
api: api,
basedir: path.join(__dirname, 'fixtures'),
handlers: path.join(__dirname, 'fixtures/handlers'),
security: path.join(__dirname, 'fixtures/extensions')
});

t.strictEqual(routes.length, 4, 'added 4 routes.');

Expand All @@ -33,6 +38,20 @@ test('routebuilder', function (t) {
t.end();
});

t.test('security definitions', function (t) {
var route;

t.plan(5);

route = routes[1];
t.ok(route.security, 'has security definition');
t.ok(route.security.default && Array.isArray(route.security.default.scopes), 'default has scopes.');
t.ok(route.security.default && typeof route.security.default.authorize === 'function', 'default has an authorize function.');
//options.security
t.ok(route.security.secondary && Array.isArray(route.security.secondary.scopes), 'secondary has scopes.');
t.ok(route.security.secondary && typeof route.security.secondary.authorize === 'function', 'secondary has an authorize function.');
});

t.test('build from x-handler', function (t) {
routes = buildroutes({ api: api, basedir: path.join(__dirname, 'fixtures')});

Expand Down Expand Up @@ -129,18 +148,6 @@ test('routebuilder', function (t) {
t.end();
});

t.test('security definitions', function (t) {
var route;

t.plan(3);

route = routes[1];

t.ok(route.security, 'has security definition');
t.ok(route.security.default && Array.isArray(route.security.default.scopes), 'has scopes.');
t.ok(route.security.default && typeof route.security.default.authorize === 'function', 'has an authorize function.');
});

t.test('bad dir', function (t) {
t.plan(1);

Expand Down

0 comments on commit c86b073

Please sign in to comment.