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:
@@ -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 ""
|
||||
|
||||
13
docker/configurator/helpers.js
Normal file
13
docker/configurator/helpers.js
Normal 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")
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
43
docker/configurator/oauth.js
Normal file
43
docker/configurator/oauth.js
Normal 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 ``
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
58
test/docker/oauth.js
Normal 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" },
|
||||
})`))
|
||||
})
|
||||
})
|
||||
@@ -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())
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user