Skip to content
This repository has been archived by the owner on Dec 27, 2024. It is now read-only.

Commit

Permalink
feat: support for auth method at operation/request level Close #40
Browse files Browse the repository at this point in the history
  • Loading branch information
joolfe committed Jun 5, 2021
1 parent 76e8b94 commit a8c4d17
Show file tree
Hide file tree
Showing 9 changed files with 464 additions and 49 deletions.
11 changes: 6 additions & 5 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,12 @@
* Postman variables as Path parameters.
* Automatic infer types from query and headers parameters.
* Support Json and Text body formats.
* Global Authorization parse or by configuration (Basic and Bearer).
* Postman Authorization parse or by configuration (Basic and Bearer).
* Contact and License from variables or by configuration.
* Provide meta-information as a markdown table.
* Path depth configuration.
* Response status code parse from test.
* [x-logo](https://github.com/Redocly/redoc/blob/master/docs/redoc-vendor-extensions.md#x-logo) extension support

See [Features](#features) section for more details about how to use each of this features.

Expand Down Expand Up @@ -186,7 +187,7 @@ The default value is `0`, so all prefix will be added to Open APi operations Pat

### auth (Object)

The global authorization info can be parse from the Postman collection as described in [Global authorization](#global-authorization) section, but you can customize this info using the `auth` option, this param is a Object that follow the structure of OpenAPI [Security Scheme](http://spec.openapis.org/oas/v3.0.3.html#security-scheme-object), in this moment only type `http` is supported and schemes `basic` and `bearer`, as an example of this option:
The global authorization info can be parse from the Postman collection as described in [Postman authorization](#postman-authorization) section, but you can customize this info using the `auth` option, this param is a Object that follow the structure of OpenAPI [Security Scheme](http://spec.openapis.org/oas/v3.0.3.html#security-scheme-object), in this moment only type `http` is supported and schemes `basic` and `bearer`, as an example of this option:

```js
{
Expand Down Expand Up @@ -339,15 +340,15 @@ For headers and query fields you can indicate that this parameter is mandatory/r

Have a look to the [GetMethods collection](https://github.com/joolfe/postman-to-openapi/blob/master/test/resources/input/v21/GetMethods.json), [Headers collection](https://github.com/joolfe/postman-to-openapi/blob/master/test/resources/input/v21/Headers.json) and [PathParams collection](https://github.com/joolfe/postman-to-openapi/blob/master/test/resources/input/v21/PathParams.json) files for examples of how to use this features.

## Global authorization
## Postman authorization

The OpenAPI root [security](http://spec.openapis.org/oas/v3.0.3.html#openapi-object) definition is filled using the authorization method defined at Postman Collection [authorization config](https://learning.postman.com/docs/sending-requests/authorization/#inheriting-auth).

Only types 'Basic Auth' and 'Bearer Token' are supported now and not operation individual definition is supported.
Only types 'Basic Auth' and 'Bearer Token' are supported by now. If you define an authorization at postman request level this will overwrite the global defined for this OpenAPI operation.

You can customize the global authorization definition using the [Auth option](#auth-(object)).

Have a look to the collections [AuthBasic](https://github.com/joolfe/postman-to-openapi/blob/master/test/resources/input/v21/AuthBasic.json) and [AuthBearer](https://github.com/joolfe/postman-to-openapi/blob/master/test/resources/input/v21/AuthBearer.json) for examples of how to use this feature.
Have a look to the collections [AuthBasic](https://github.com/joolfe/postman-to-openapi/blob/master/test/resources/input/v21/AuthBasic.json), [AuthBearer](https://github.com/joolfe/postman-to-openapi/blob/master/test/resources/input/v21/AuthBearer.json) and [AuthMultiple](https://github.com/joolfe/postman-to-openapi/blob/master/test/resources/input/v21/AuthMultiple.json) for examples of how to use this feature.

## Global servers configuration

Expand Down
60 changes: 38 additions & 22 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const { version } = require('../package.json')

async function postmanToOpenApi (input, output, {
info = {}, defaultTag = 'default', pathDepth = 0,
auth, servers, externalDocs = {}, folders = {}
auth: optsAuth, servers, externalDocs = {}, folders = {}
} = {}) {
// TODO validate?
const collectionFile = await readFile(input)
Expand All @@ -16,6 +16,7 @@ async function postmanToOpenApi (input, output, {
const paths = {}
const domains = new Set()
const tags = {}
const securitySchemes = {}

for (let [i, element] of items.entries()) {
while (element.item != null) { // is a folder
Expand All @@ -28,7 +29,7 @@ async function postmanToOpenApi (input, output, {
element = (tagged.length > 0) ? tagged.shift() : items[i]
}
const {
request: { url, method, body, description: rawDesc, header },
request: { url, method, body, description: rawDesc, header, auth },
name: summary, tag = defaultTag, event: events
} = element
const { path, query, protocol, host, port, valid } = scrapeURL(url)
Expand All @@ -37,12 +38,12 @@ async function postmanToOpenApi (input, output, {
const joinedPath = calculatePath(path, pathDepth)
if (!paths[joinedPath]) paths[joinedPath] = {}
const { description, paramsMeta } = descriptionParse(rawDesc)

paths[joinedPath][method.toLowerCase()] = {
tags: [tag],
summary,
...(description ? { description } : {}),
...parseBody(body, method),
...parseOperationAuth(auth, securitySchemes, optsAuth),
...parseParameters(query, header, joinedPath, paramsMeta),
responses: parseResponse(events)
}
Expand All @@ -54,7 +55,7 @@ async function postmanToOpenApi (input, output, {
info: compileInfo(postmanJson, info),
...parseExternalDocs(variable, externalDocs),
...parseServers(domains, servers),
...parseAuth(postmanJson, auth),
...parseAuth(postmanJson, optsAuth, securitySchemes),
...parseTags(tags),
paths
}
Expand Down Expand Up @@ -226,31 +227,46 @@ function inferType (value) {
}

/* Calculate the global auth based on options and postman definition */
function parseAuth ({ auth }, optAuth) {
function parseAuth ({ auth }, optAuth, securitySchemes) {
if (optAuth != null) {
return parseOptsAuth(optAuth)
}
return parsePostmanAuth(auth)
return parsePostmanAuth(auth, securitySchemes)
}

/* Parse a postman auth definition */
function parsePostmanAuth (postmanAuth = {}) {
function parsePostmanAuth (postmanAuth = {}, securitySchemes) {
const { type } = postmanAuth
return (type != null)
? {
components: {
securitySchemes: {
[type + 'Auth']: {
type: 'http',
scheme: type
}
}
},
security: [{
[type + 'Auth']: []
}]
}
: {}
if (type != null) {
securitySchemes[`${type}Auth`] = {
type: 'http',
scheme: type
}
return {
components: { securitySchemes },
security: [{
[`${type}Auth`]: []
}]
}
}
return (Object.keys(securitySchemes).length === 0) ? {} : { components: { securitySchemes } }
}

/* Parse Auth at operation/request level */
function parseOperationAuth (auth, securitySchemes, optsAuth) {
if (auth == null || optsAuth != null) {
// In case of config auth operation auth is disabled
return {}
} else {
const { type } = auth
securitySchemes[`${type}Auth`] = {
type: 'http',
scheme: type
}
return {
security: [{ [`${type}Auth`]: [] }]
}
}
}

/* Parse a options global auth */
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "postman-to-openapi",
"version": "1.11.0",
"version": "1.12.0",
"description": "Convert postman collection to OpenAPI spec",
"main": "lib/index.js",
"types": "types/index.d.ts",
Expand Down
52 changes: 33 additions & 19 deletions test/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const EXPECTED_HEADERS = readFileSync('./test/resources/output/Headers.yml', 'ut
const EXPECTED_AUTH_BEARER = readFileSync('./test/resources/output/AuthBearer.yml', 'utf8')
const EXPECTED_AUTH_BASIC = readFileSync('./test/resources/output/AuthBasic.yml', 'utf8')
const EXPECTED_BASIC_WITH_AUTH = readFileSync('./test/resources/output/BasicWithAuth.yml', 'utf8')
const EXPECTED_AUTH_MULTIPLE = readFileSync('./test/resources/output/AuthMultiple.yml', 'utf8')
const EXPECTED_PATH_PARAMS = readFileSync('./test/resources/output/PathParams.yml', 'utf8')
const EXPECTED_MULTIPLE_SERVERS = readFileSync('./test/resources/output/MultipleServers.yml', 'utf8')
const EXPECTED_SERVERS_OPTIONS = readFileSync('./test/resources/output/ServersOpts.yml', 'utf8')
Expand All @@ -42,6 +43,26 @@ const EXPECTED_EXTERNAL_DOCS_OPTS_PARTIAL = readFileSync('./test/resources/outpu
const EXPECTED_EMPTY_URL = readFileSync('./test/resources/output/EmptyUrl.yml', 'utf8')
const EXPECTED_X_LOGO = readFileSync('./test/resources/output/XLogo.yml', 'utf8')
const EXPECTED_X_LOGO_VAR = readFileSync('./test/resources/output/XLogoVar.yml', 'utf8')
const EXPECTED_AUTH_OPTIONS = readFileSync('./test/resources/output/AuthOptions.yml', 'utf8')

const AUTH_DEFINITIONS = {
myCustomAuth: {
type: 'http',
scheme: 'bearer',
bearerFormat: 'A resource owner JWT',
description: 'My awesome authentication using bearer'
},
myCustomAuth2: {
type: 'http',
scheme: 'basic',
description: 'My awesome authentication using user and password'
},
notSupported: {
type: 'http',
scheme: 'digest',
description: 'Not supported security'
}
}

describe('Library specs', function () {
afterEach('remove file', function () {
Expand Down Expand Up @@ -73,6 +94,7 @@ describe('Library specs', function () {
const COLLECTION_EXTERNAL_DOCS = `./test/resources/input/${version}/ExternalDocs.json`
const COLLECTION_EMPTY_URL = `./test/resources/input/${version}/EmptyUrl.json`
const COLLECTION_XLOGO = `./test/resources/input/${version}/XLogo.json`
const COLLECTION_MULTI_AUTH = `./test/resources/input/${version}/AuthMultiple.json`

it('should work with a basic transform', async function () {
const result = await postmanToOpenApi(COLLECTION_BASIC, OUTPUT_PATH, {})
Expand Down Expand Up @@ -265,25 +287,7 @@ describe('Library specs', function () {
})

it('should use global authorization by configuration', async function () {
const authDefinition = {
myCustomAuth: {
type: 'http',
scheme: 'bearer',
bearerFormat: 'A resource owner JWT',
description: 'My awesome authentication using bearer'
},
myCustomAuth2: {
type: 'http',
scheme: 'basic',
description: 'My awesome authentication using user and password'
},
notSupported: {
type: 'http',
scheme: 'digest',
description: 'Not supported security'
}
}
const result = await postmanToOpenApi(COLLECTION_BASIC, OUTPUT_PATH, { auth: authDefinition })
const result = await postmanToOpenApi(COLLECTION_BASIC, OUTPUT_PATH, { auth: AUTH_DEFINITIONS })
equal(result, EXPECTED_BASIC_WITH_AUTH)
})

Expand Down Expand Up @@ -354,6 +358,16 @@ describe('Library specs', function () {
const result = await postmanToOpenApi(COLLECTION_XLOGO, OUTPUT_PATH, {})
equal(result, EXPECTED_X_LOGO_VAR)
})

it('should support auth definition at request level', async function () {
const result = await postmanToOpenApi(COLLECTION_MULTI_AUTH, OUTPUT_PATH, {})
equal(result, EXPECTED_AUTH_MULTIPLE)
})

it('should ignore operational auth when auth options are provided', async function () {
const result = await postmanToOpenApi(COLLECTION_MULTI_AUTH, OUTPUT_PATH, { auth: AUTH_DEFINITIONS })
equal(result, EXPECTED_AUTH_OPTIONS)
})
})
})

Expand Down
104 changes: 104 additions & 0 deletions test/resources/input/v2/AuthMultiple.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
{
"info": {
"_postman_id": "0444d7cb-6825-4cb5-bde0-4439e33eb825",
"name": "AuthBearer",
"description": "Collection to test authorization global",
"schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json"
},
"item": [
{
"name": "Create new User Copy",
"request": {
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"raw": "{\n \"example\": \"field\",\n \"other\": {\n \"data1\": \"yes\",\n \"data2\": \"no\"\n }\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": "https://api.io/users",
"description": "Create a new user into your amazing API"
},
"response": []
},
{
"name": "Get list of users",
"request": {
"auth": {
"type": "basic",
"basic": {
"password": "myPassword",
"username": "myUser"
}
},
"method": "GET",
"header": [],
"url": {
"raw": "https://api.io/users?age=45&name=Jhon&review=true&number=23.56",
"protocol": "https",
"host": [
"api",
"io"
],
"path": [
"users"
],
"query": [
{
"key": "age",
"value": "45",
"description": "Filter by age"
},
{
"key": "name",
"value": "Jhon",
"description": "Filter by name"
},
{
"key": "review",
"value": "true",
"description": "Indicate if should be reviewed or not"
},
{
"key": "number",
"value": "23.56",
"description": "This is a number"
}
]
},
"description": "Obtain a list of users that fullfill the conditions of the filters"
},
"response": []
}
],
"auth": {
"type": "bearer",
"bearer": {
"token": "YUIHUILOIJOJOJ"
}
},
"event": [
{
"listen": "prerequest",
"script": {
"type": "text/javascript",
"exec": [
""
]
}
},
{
"listen": "test",
"script": {
"type": "text/javascript",
"exec": [
""
]
}
}
]
}
Loading

0 comments on commit a8c4d17

Please sign in to comment.