feature: Docker OAuth block support (via #4987)

* add `onFound` callback to schemas
* add warning to method docs (for #4957)
* implement Docker OAuth2 init block support
* update docs
* add OAUTH_SCOPE_SEPARATOR
* drop OAuth env from Dockerfile and run script
* don't indent the first oauth block line
* drop unused `dedent` import
* touch up warning message
* add more test cases
* return an empty block if no OAuth content is generated
* fix broken doc line
This commit is contained in:
kyle
2018-11-01 18:52:13 -04:00
committed by GitHub
parent 31a8b13777
commit c6eb8edb5f
10 changed files with 283 additions and 160 deletions

View File

@@ -9,11 +9,6 @@ RUN apk add nodejs
LABEL maintainer="fehguy"
ENV API_KEY "**None**"
ENV OAUTH_CLIENT_ID "**None**"
ENV OAUTH_CLIENT_SECRET "**None**"
ENV OAUTH_REALM "**None**"
ENV OAUTH_APP_NAME "**None**"
ENV OAUTH_ADDITIONAL_PARAMS "**None**"
ENV SWAGGER_JSON "/app/swagger.json"
ENV PORT 8080
ENV BASE_URL ""

View File

@@ -0,0 +1,13 @@
module.exports.indent = function indent(str, len, fromLine = 0) {
return str
.split("\n")
.map((line, i) => {
if (i + 1 >= fromLine) {
return `${Array(len + 1).join(" ")}${line}`
} else {
return line
}
})
.join("\n")
}

View File

@@ -2,7 +2,8 @@ const fs = require("fs")
const path = require("path")
const translator = require("./translator")
const configSchema = require("./variables")
const oauthBlockBuilder = require("./oauth")
const indent = require("./helpers").indent
const START_MARKER = "// Begin Swagger UI call region"
const END_MARKER = "// End Swagger UI call region"
@@ -22,19 +23,7 @@ fs.writeFileSync(targetPath, `${beforeStartMarkerContent}
const ui = SwaggerUIBundle({
${indent(translator(process.env, { injectBaseConfig: true }), 8, 2)}
})
${indent(oauthBlockBuilder(process.env), 6, 2)}
${END_MARKER}
${afterEndMarkerContent}`)
function indent(str, len, fromLine) {
return str
.split("\n")
.map((line, i) => {
if(i + 1 >= fromLine) {
return `${Array(len + 1).join(" ")}${line}`
} else {
return line
}
})
.join("\n")
}

View File

@@ -0,0 +1,43 @@
const translator = require("./translator")
const indent = require("./helpers").indent
const oauthBlockSchema = {
OAUTH_CLIENT_ID: {
type: "string",
name: "clientId"
},
OAUTH_CLIENT_SECRET: {
type: "string",
name: "clientSecret",
onFound: () => console.warn("Swagger UI warning: don't use `OAUTH_CLIENT_SECRET` in production!")
},
OAUTH_REALM: {
type: "string",
name: "realm"
},
OAUTH_APP_NAME: {
type: "string",
name: "appName"
},
OAUTH_SCOPE_SEPARATOR: {
type: "string",
name: "scopeSeparator"
},
OAUTH_ADDITIONAL_PARAMS: {
type: "object",
name: "additionalQueryStringParams"
}
}
module.exports = function oauthBlockBuilder(env) {
const translatorResult = translator(env, { schema: oauthBlockSchema })
if(translatorResult) {
return (
`ui.initOAuth({
${indent(translatorResult, 2)}
})`)
}
return ``
}

View File

@@ -55,6 +55,10 @@ function objectToKeyValueString(env, { injectBaseConfig = false, schema = config
if(!varSchema) return
if(varSchema.onFound) {
varSchema.onFound()
}
const storageContents = valueStorage[varSchema.name]
if(storageContents) {

View File

@@ -28,13 +28,6 @@ if [ "${BASE_URL}" ]; then
fi
replace_in_index myApiKeyXXXX123456789 $API_KEY
replace_or_delete_in_index your-client-id $OAUTH_CLIENT_ID
replace_or_delete_in_index your-client-secret-if-required $OAUTH_CLIENT_SECRET
replace_or_delete_in_index your-realms $OAUTH_REALM
replace_or_delete_in_index your-app-name $OAUTH_APP_NAME
if [ "$OAUTH_ADDITIONAL_PARAMS" != "**None**" ]; then
replace_in_index "additionalQueryStringParams: {}" "additionalQueryStringParams: {$OAUTH_ADDITIONAL_PARAMS}"
fi
if [[ -f $SWAGGER_JSON ]]; then
cp -s $SWAGGER_JSON $NGINX_ROOT

View File

@@ -81,9 +81,11 @@ Parameter name | Docker variable | Description
### Instance methods
Parameter name | Docker variable | Description
**💡 Take note! These are methods, not parameters**.
Method name | Docker variable | Description
--- | --- | -----
<a name="initOAuth"></a>`initOAuth` | _Unavailable_ | `(configObj) => void`. Provide Swagger-UI with information about your OAuth server - see the OAuth2 documentation for more information.
<a name="initOAuth"></a>`initOAuth` | [_See `oauth2.md`_](./oauth2.md) | `(configObj) => void`. Provide Swagger-UI with information about your OAuth server - see the OAuth2 documentation for more information.
<a name="preauthorizeBasic"></a>`preauthorizeBasic` | _Unavailable_ | `(authDefinitionKey, username, password) => action`. Programmatically set values for a Basic authorization scheme.
<a name="preauthorizeApiKey"></a>`preauthorizeApiKey` | _Unavailable_ | `(authDefinitionKey, apiKeyValue) => action`. Programmatically set values for an API key authorization scheme.

View File

@@ -1,15 +1,15 @@
# OAuth2 configuration
You can configure OAuth2 authorization by calling the `initOAuth` method.
Config Name | Description
--- | ---
clientId | Default clientId. MUST be a string
clientSecret | **🚨 Never use this parameter in your production environemnt. It exposes cruicial security information. This feature is intended for dev/test environments only. 🚨** <br>Default clientSecret. MUST be a string
realm | realm query parameter (for oauth1) added to `authorizationUrl` and `tokenUrl`. MUST be a string
appName | application name, displayed in authorization popup. MUST be a string
scopeSeparator | scope separator for passing scopes, encoded before calling, default value is a space (encoded value `%20`). MUST be a string
additionalQueryStringParams | Additional query parameters added to `authorizationUrl` and `tokenUrl`. MUST be an object
useBasicAuthenticationWithAccessCodeGrant | Only activated for the `accessCode` flow. During the `authorization_code` request to the `tokenUrl`, pass the [Client Password](https://tools.ietf.org/html/rfc6749#section-2.3.1) using the HTTP Basic Authentication scheme (`Authorization` header with `Basic base64encode(client_id + client_secret)`). The default is `false`
Property name | Docker variable | Description
--- | --- | ------
clientId | `OAUTH_CLIENT_ID` | Default clientId. MUST be a string
clientSecret | `OAUTH_CLIENT_SECRET` | **🚨 Never use this parameter in your production environemnt. It exposes cruicial security information. This feature is intended for dev/test environments only. 🚨** <br>Default clientSecret. MUST be a string
realm | `OAUTH_REALM` |realm query parameter (for oauth1) added to `authorizationUrl` and `tokenUrl`. MUST be a string
appName | `OAUTH_APP_NAME` |application name, displayed in authorization popup. MUST be a string
scopeSeparator | `OAUTH_SCOPE_SEPARATOR` |scope separator for passing scopes, encoded before calling, default value is a space (encoded value `%20`). MUST be a string
additionalQueryStringParams | `OAUTH_ADDITIONAL_PARAMS` |Additional query parameters added to `authorizationUrl` and `tokenUrl`. MUST be an object
useBasicAuthenticationWithAccessCodeGrant | _Unavailable_ |Only activated for the `accessCode` flow. During the `authorization_code` request to the `tokenUrl`, pass the [Client Password](https://tools.ietf.org/html/rfc6749#section-2.3.1) using the HTTP Basic Authentication scheme (`Authorization` header with `Basic base64encode(client_id + client_secret)`). The default is `false`
```javascript
const ui = SwaggerUI({...})

58
test/docker/oauth.js Normal file
View File

@@ -0,0 +1,58 @@
const expect = require("expect")
const oauthBlockBuilder = require("../../docker/configurator/oauth")
const dedent = require("dedent")
describe("docker: env translator - oauth block", function() {
it("should omit the block if there are no valid keys", function () {
const input = {}
expect(oauthBlockBuilder(input)).toEqual(``)
})
it("should omit the block if there are no valid keys", function () {
const input = {
NOT_A_VALID_KEY: "asdf1234"
}
expect(oauthBlockBuilder(input)).toEqual(``)
})
it("should generate a block from empty values", function() {
const input = {
OAUTH_CLIENT_ID: ``,
OAUTH_CLIENT_SECRET: ``,
OAUTH_REALM: ``,
OAUTH_APP_NAME: ``,
OAUTH_SCOPE_SEPARATOR: "",
OAUTH_ADDITIONAL_PARAMS: ``,
}
expect(oauthBlockBuilder(input)).toEqual(dedent(`
ui.initOAuth({
clientId: "",
clientSecret: "",
realm: "",
appName: "",
scopeSeparator: "",
additionalQueryStringParams: undefined,
})`))
})
it("should generate a full block", function() {
const input = {
OAUTH_CLIENT_ID: `myId`,
OAUTH_CLIENT_SECRET: `mySecret`,
OAUTH_REALM: `myRealm`,
OAUTH_APP_NAME: `myAppName`,
OAUTH_SCOPE_SEPARATOR: "%21",
OAUTH_ADDITIONAL_PARAMS: `{ "a": 1234, "b": "stuff" }`,
}
expect(oauthBlockBuilder(input)).toEqual(dedent(`
ui.initOAuth({
clientId: "myId",
clientSecret: "mySecret",
realm: "myRealm",
appName: "myAppName",
scopeSeparator: "%21",
additionalQueryStringParams: { "a": 1234, "b": "stuff" },
})`))
})
})

View File

@@ -3,13 +3,38 @@ const translator = require("../../docker/configurator/translator")
const dedent = require("dedent")
describe("docker: env translator", function() {
it("should generate an empty baseline config", function() {
describe("fundamentals", function() {
it("should generate an empty baseline config", function () {
const input = {}
expect(translator(input)).toEqual(``)
})
it("should generate a base config including the base content", function() {
it("should call an onFound callback", function () {
const input = {
MY_THING: "hey"
}
const onFoundSpy = expect.createSpy()
const schema = {
MY_THING: {
type: "string",
name: "myThing",
onFound: onFoundSpy
}
}
const res = translator(input, {
schema
})
expect(res).toEqual(`myThing: "hey",`)
expect(onFoundSpy.calls.length).toEqual(1)
})
})
describe("Swagger UI configuration", function() {
it("should generate a base config including the base content", function () {
const input = {}
expect(translator(input, {
@@ -29,7 +54,7 @@ describe("docker: env translator", function() {
`))
})
it("should ignore an unknown config", function() {
it("should ignore an unknown config", function () {
const input = {
ASDF1234: "wow hello"
}
@@ -37,7 +62,7 @@ describe("docker: env translator", function() {
expect(translator(input)).toEqual(dedent(``))
})
it("should generate a string config", function() {
it("should generate a string config", function () {
const input = {
URL: "http://petstore.swagger.io/v2/swagger.json",
FILTER: ""
@@ -49,7 +74,7 @@ describe("docker: env translator", function() {
).trim())
})
it("should generate a boolean config", function() {
it("should generate a boolean config", function () {
const input = {
DEEP_LINKING: "true",
SHOW_EXTENSIONS: "false",
@@ -63,7 +88,7 @@ describe("docker: env translator", function() {
))
})
it("should generate an object config", function() {
it("should generate an object config", function () {
const input = {
SPEC: `{ swagger: "2.0" }`
}
@@ -73,7 +98,7 @@ describe("docker: env translator", function() {
).trim())
})
it("should generate an array config", function() {
it("should generate an array config", function () {
const input = {
URLS: `["/one", "/two"]`,
SUPPORTED_SUBMIT_METHODS: ""
@@ -97,7 +122,7 @@ describe("docker: env translator", function() {
).trim())
})
it("should disregard a legacy variable in favor of a regular one", function() {
it("should disregard a legacy variable in favor of a regular one", function () {
const input = {
// Order is important to this test... legacy vars should be
// superseded regardless of what is fed in first.
@@ -113,7 +138,7 @@ describe("docker: env translator", function() {
).trim())
})
it("should generate a full config k:v string", function() {
it("should generate a full config k:v string", function () {
const input = {
API_URL: "/old.yaml",
API_URLS: `["/old", "/older"]`,
@@ -167,7 +192,7 @@ describe("docker: env translator", function() {
).trim())
})
it("should generate a full config k:v string including base config", function() {
it("should generate a full config k:v string including base config", function () {
const input = {
API_URL: "/old.yaml",
API_URLS: `["/old", "/older"]`,
@@ -227,4 +252,5 @@ describe("docker: env translator", function() {
validatorUrl: "http://smartbear.com/",`
).trim())
})
})
})