Merge branch 'master' into ft/docs

This commit is contained in:
kyle
2017-10-31 17:21:25 -07:00
committed by GitHub
54 changed files with 870 additions and 343 deletions

View File

@@ -16,7 +16,8 @@
"extends": ["eslint:recommended", "plugin:react/recommended"], "extends": ["eslint:recommended", "plugin:react/recommended"],
"plugins": [ "plugins": [
"react" "react",
"mocha"
], ],
"rules": { "rules": {
@@ -32,6 +33,7 @@
"comma-dangle": 0, "comma-dangle": 0,
"no-console": ["error", { allow: ["warn", "error"] }], "no-console": ["error", { allow: ["warn", "error"] }],
"react/jsx-no-bind": 1, "react/jsx-no-bind": 1,
"react/display-name": 0 "react/display-name": 0,
"mocha/no-exclusive-tests": "error"
} }
} }

View File

@@ -1,6 +1,9 @@
<!--- <!---
Thanks for filing an issue 😄 ! Before you submit, please read the following: Thanks for filing an issue 😄 ! Before you submit, please read the following:
If you're here to report a security issue, please STOP writing an issue and contact us
at security@swagger.io instead!
Search open/closed issues before submitting since someone might have asked the same thing before! Search open/closed issues before submitting since someone might have asked the same thing before!
Issues on GitHub are only related to problems of Swagger-UI itself. We'll try to offer support Issues on GitHub are only related to problems of Swagger-UI itself. We'll try to offer support

42
.github/pull_request_template.md vendored Normal file
View File

@@ -0,0 +1,42 @@
<!--- Provide a general summary of your changes in the Title above -->
### Description
<!--- Describe your changes in detail -->
### Motivation and Context
<!--- Why is this change required? What problem does it solve? -->
<!--- If it fixes an open issue, please link to the issue here. -->
<!--- Use the magic "Fixes #1234" format, so the issues are -->
<!--- automatically closed when this PR is merged. -->
### How Has This Been Tested?
<!--- Please describe in detail how you manually tested your changes. -->
<!--- Include details of your testing environment, and the tests you ran to -->
<!--- see how your change affects other areas of the code, etc. -->
### Screenshots (if appropriate):
### Types of changes
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
- [ ] No code changes (changes to documentation, CI, metadata, etc)
- [ ] Dependency changes (any modification to dependencies in `package.json`)
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
### Checklist:
<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
- [ ] My code follows the code style of this project.
- [ ] My change requires a change to the documentation.
- [ ] I have updated the documentation accordingly.
- [ ] I have added tests to cover my changes.
- [ ] All new and existing tests passed.

View File

@@ -6,7 +6,7 @@
**This is the new version of swagger-ui, 3.x. Want to learn more? Check out our [FAQ](http://swagger.io/new-ui-faq/).** **This is the new version of swagger-ui, 3.x. Want to learn more? Check out our [FAQ](http://swagger.io/new-ui-faq/).**
**👉🏼 Want to score an easy open-source contribution?** Check out our [Good first contribution](https://github.com/swagger-api/swagger-ui/issues?q=is%3Aissue+is%3Aopen+label%3A%22Good+first+contribution%22) label. **👉🏼 Want to score an easy open-source contribution?** Check out our [Good first issue](https://github.com/swagger-api/swagger-ui/issues?q=is%3Aissue+is%3Aopen+label%3A%22Good+first+issue%22) label.
As a brand new version, written from the ground up, there are some known issues and unimplemented features. Check out the [Known Issues](#known-issues) section for more details. As a brand new version, written from the ground up, there are some known issues and unimplemented features. Check out the [Known Issues](#known-issues) section for more details.
@@ -22,7 +22,7 @@ The OpenAPI Specification has undergone 5 revisions since initial creation in 20
Swagger UI Version | Release Date | OpenAPI Spec compatibility | Notes Swagger UI Version | Release Date | OpenAPI Spec compatibility | Notes
------------------ | ------------ | -------------------------- | ----- ------------------ | ------------ | -------------------------- | -----
3.3.1 | 2017-10-02 | 2.0, 3.0 | [tag v3.3.1](https://github.com/swagger-api/swagger-ui/tree/v3.3.1) 3.4.2 | 2017-10-30 | 2.0, 3.0 | [tag v3.4.2](https://github.com/swagger-api/swagger-ui/tree/v3.4.2)
3.0.21 | 2017-07-26 | 2.0 | [tag v3.0.21](https://github.com/swagger-api/swagger-ui/tree/v3.0.21) 3.0.21 | 2017-07-26 | 2.0 | [tag v3.0.21](https://github.com/swagger-api/swagger-ui/tree/v3.0.21)
2.2.10 | 2017-01-04 | 1.1, 1.2, 2.0 | [tag v2.2.10](https://github.com/swagger-api/swagger-ui/tree/v2.2.10) 2.2.10 | 2017-01-04 | 1.1, 1.2, 2.0 | [tag v2.2.10](https://github.com/swagger-api/swagger-ui/tree/v2.2.10)
2.1.5 | 2016-07-20 | 1.1, 1.2, 2.0 | [tag v2.1.5](https://github.com/swagger-api/swagger-ui/tree/v2.1.5) 2.1.5 | 2016-07-20 | 1.1, 1.2, 2.0 | [tag v2.1.5](https://github.com/swagger-api/swagger-ui/tree/v2.1.5)

View File

@@ -3,6 +3,7 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Swagger UI</title> <title>Swagger UI</title>
<link href="https://fonts.googleapis.com/css?family=Open+Sans:400,700|Source+Code+Pro:300,600|Titillium+Web:400,600,700" rel="stylesheet"> <link href="https://fonts.googleapis.com/css?family=Open+Sans:400,700|Source+Code+Pro:300,600|Titillium+Web:400,600,700" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="./swagger-ui.css" > <link rel="stylesheet" type="text/css" href="./swagger-ui.css" >

View File

@@ -27,7 +27,10 @@
isValid = qp.state === sentState isValid = qp.state === sentState
if (oauth2.auth.schema.get("flow") === "accessCode" && !oauth2.auth.code) { if ((
oauth2.auth.schema.get("flow") === "accessCode"||
oauth2.auth.schema.get("flow") === "authorizationCode"
) && !oauth2.auth.code) {
if (!isValid) { if (!isValid) {
oauth2.errCb({ oauth2.errCb({
authId: oauth2.auth.name, authId: oauth2.auth.name,

View File

@@ -27,7 +27,10 @@
isValid = qp.state === sentState isValid = qp.state === sentState
if (oauth2.auth.schema.get("flow") === "accessCode" && !oauth2.auth.code) { if ((
oauth2.auth.schema.get("flow") === "accessCode"||
oauth2.auth.schema.get("flow") === "authorizationCode"
) && !oauth2.auth.code) {
if (!isValid) { if (!isValid) {
oauth2.errCb({ oauth2.errCb({
authId: oauth2.auth.name, authId: oauth2.auth.name,

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
dist/swagger-ui.css vendored

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
{"version":3,"file":"swagger-ui.css","sources":[],"mappings":"","sourceRoot":""} {"version":3,"sources":[],"names":[],"mappings":"","file":"swagger-ui.css","sourceRoot":""}

4
dist/swagger-ui.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -32,7 +32,7 @@ fi
if [[ -f $SWAGGER_JSON ]]; then if [[ -f $SWAGGER_JSON ]]; then
cp $SWAGGER_JSON $NGINX_ROOT cp $SWAGGER_JSON $NGINX_ROOT
REL_PATH="/$(basename $SWAGGER_JSON)" REL_PATH="./$(basename $SWAGGER_JSON)"
sed -i "s|http://petstore.swagger.io/v2/swagger.json|$REL_PATH|g" $INDEX_FILE sed -i "s|http://petstore.swagger.io/v2/swagger.json|$REL_PATH|g" $INDEX_FILE
sed -i "s|http://example.com/api|$REL_PATH|g" $INDEX_FILE sed -i "s|http://example.com/api|$REL_PATH|g" $INDEX_FILE
else else

View File

@@ -137,7 +137,7 @@ module.exports = function(rules, options) {
} }
}, },
devtool: specialOptions.sourcemaps ? "cheap-module-source-map" : null, devtool: specialOptions.sourcemaps ? "nosource-source-map" : null,
plugins, plugins,

View File

@@ -1,6 +1,6 @@
{ {
"name": "swagger-ui", "name": "swagger-ui",
"version": "3.3.1", "version": "3.4.2",
"main": "dist/swagger-ui.js", "main": "dist/swagger-ui.js",
"repository": "git@github.com:swagger-api/swagger-ui.git", "repository": "git@github.com:swagger-api/swagger-ui.git",
"contributors": [ "contributors": [
@@ -39,6 +39,7 @@
"e2e": "npm-run-all --parallel -r hot-server mock-api test-e2e" "e2e": "npm-run-all --parallel -r hot-server mock-api test-e2e"
}, },
"dependencies": { "dependencies": {
"@braintree/sanitize-url": "^2.0.2",
"base64-js": "^1.2.0", "base64-js": "^1.2.0",
"brace": "0.7.0", "brace": "0.7.0",
"classnames": "^2.2.5", "classnames": "^2.2.5",
@@ -78,7 +79,7 @@
"scroll-to-element": "^2.0.0", "scroll-to-element": "^2.0.0",
"serialize-error": "2.0.0", "serialize-error": "2.0.0",
"shallowequal": "0.2.2", "shallowequal": "0.2.2",
"swagger-client": "^3.2.0", "swagger-client": "^3.3.1",
"url-parse": "^1.1.8", "url-parse": "^1.1.8",
"whatwg-fetch": "0.11.1", "whatwg-fetch": "0.11.1",
"worker-loader": "^0.7.1", "worker-loader": "^0.7.1",
@@ -105,6 +106,7 @@
"enzyme": "^2.7.1", "enzyme": "^2.7.1",
"eslint": "^4.1.1", "eslint": "^4.1.1",
"eslint-plugin-import": "^2.6.0", "eslint-plugin-import": "^2.6.0",
"eslint-plugin-mocha": "^4.11.0",
"eslint-plugin-react": "^7.1.0", "eslint-plugin-react": "^7.1.0",
"extract-text-webpack-plugin": "^2.1.2", "extract-text-webpack-plugin": "^2.1.2",
"file-loader": "0.11.2", "file-loader": "0.11.2",

View File

@@ -51,14 +51,15 @@ export default class ApiKeyAuth extends React.Component {
return ( return (
<div> <div>
<h4>Api key authorization<JumpToPath path={[ "securityDefinitions", name ]} /></h4> <h4>
<code>{ name || schema.get("name") }</code>&nbsp;
(apiKey)
<JumpToPath path={[ "securityDefinitions", name ]} />
</h4>
{ value && <h6>Authorized</h6>} { value && <h6>Authorized</h6>}
<Row> <Row>
<Markdown source={ schema.get("description") } /> <Markdown source={ schema.get("description") } />
</Row> </Row>
<Row>
<p>Name: <code>{ schema.get("name") }</code></p>
</Row>
<Row> <Row>
<p>In: <code>{ schema.get("in") }</code></p> <p>In: <code>{ schema.get("in") }</code></p>
</Row> </Row>

View File

@@ -0,0 +1,62 @@
import React from "react"
import PropTypes from "prop-types"
import ImPropTypes from "react-immutable-proptypes"
export default class Auths extends React.Component {
static propTypes = {
schema: ImPropTypes.orderedMap.isRequired,
name: PropTypes.string.isRequired,
onAuthChange: PropTypes.func.isRequired,
authorized: ImPropTypes.orderedMap.isRequired
}
render() {
let {
schema,
name,
getComponent,
onAuthChange,
authorized,
errSelectors
} = this.props
const ApiKeyAuth = getComponent("apiKeyAuth")
const BasicAuth = getComponent("basicAuth")
let authEl
const type = schema.get("type")
switch(type) {
case "apiKey": authEl = <ApiKeyAuth key={ name }
schema={ schema }
name={ name }
errSelectors={ errSelectors }
authorized={ authorized }
getComponent={ getComponent }
onChange={ onAuthChange } />
break
case "basic": authEl = <BasicAuth key={ name }
schema={ schema }
name={ name }
errSelectors={ errSelectors }
authorized={ authorized }
getComponent={ getComponent }
onChange={ onAuthChange } />
break
default: authEl = <div key={ name }>Unknown security definition type { type }</div>
}
return (<div key={`${name}-jump`}>
{ authEl }
</div>)
}
static propTypes = {
errSelectors: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired,
authSelectors: PropTypes.object.isRequired,
specSelectors: PropTypes.object.isRequired,
authActions: PropTypes.object.isRequired,
definitions: ImPropTypes.iterable.isRequired
}
}

View File

@@ -27,7 +27,6 @@ export default class Auths extends React.Component {
e.preventDefault() e.preventDefault()
let { authActions } = this.props let { authActions } = this.props
authActions.authorize(this.state) authActions.authorize(this.state)
} }
@@ -44,8 +43,7 @@ export default class Auths extends React.Component {
render() { render() {
let { definitions, getComponent, authSelectors, errSelectors } = this.props let { definitions, getComponent, authSelectors, errSelectors } = this.props
const ApiKeyAuth = getComponent("apiKeyAuth") const AuthItem = getComponent("AuthItem")
const BasicAuth = getComponent("basicAuth")
const Oauth2 = getComponent("oauth2", true) const Oauth2 = getComponent("oauth2", true)
const Button = getComponent("Button") const Button = getComponent("Button")
@@ -64,33 +62,15 @@ export default class Auths extends React.Component {
!!nonOauthDefinitions.size && <form onSubmit={ this.submitAuth }> !!nonOauthDefinitions.size && <form onSubmit={ this.submitAuth }>
{ {
nonOauthDefinitions.map( (schema, name) => { nonOauthDefinitions.map( (schema, name) => {
let type = schema.get("type") return <AuthItem
let authEl key={name}
schema={schema}
switch(type) { name={name}
case "apiKey": authEl = <ApiKeyAuth key={ name } getComponent={getComponent}
schema={ schema } onAuthChange={this.onAuthChange}
name={ name } authorized={authorized}
errSelectors={ errSelectors } errSelectors={errSelectors}
authorized={ authorized } />
getComponent={ getComponent }
onChange={ this.onAuthChange } />
break
case "basic": authEl = <BasicAuth key={ name }
schema={ schema }
name={ name }
errSelectors={ errSelectors }
authorized={ authorized }
getComponent={ getComponent }
onChange={ this.onAuthChange } />
break
default: authEl = <div key={ name }>Unknown security definition type { type }</div>
}
return (<div key={`${name}-jump`}>
{ authEl }
</div>)
}).toArray() }).toArray()
} }
<div className="auth-btn-wrapper"> <div className="auth-btn-wrapper">

View File

@@ -2,11 +2,6 @@ import React from "react"
import PropTypes from "prop-types" import PropTypes from "prop-types"
import oauth2Authorize from "core/oauth2-authorize" import oauth2Authorize from "core/oauth2-authorize"
const IMPLICIT = "implicit"
const ACCESS_CODE = "accessCode"
const PASSWORD = "password"
const APPLICATION = "application"
export default class Oauth2 extends React.Component { export default class Oauth2 extends React.Component {
static propTypes = { static propTypes = {
name: PropTypes.string, name: PropTypes.string,
@@ -16,6 +11,7 @@ export default class Oauth2 extends React.Component {
authSelectors: PropTypes.object.isRequired, authSelectors: PropTypes.object.isRequired,
authActions: PropTypes.object.isRequired, authActions: PropTypes.object.isRequired,
errSelectors: PropTypes.object.isRequired, errSelectors: PropTypes.object.isRequired,
specSelectors: PropTypes.object.isRequired,
errActions: PropTypes.object.isRequired, errActions: PropTypes.object.isRequired,
getConfigs: PropTypes.any getConfigs: PropTypes.any
} }
@@ -83,7 +79,9 @@ export default class Oauth2 extends React.Component {
} }
render() { render() {
let { schema, getComponent, authSelectors, errSelectors, name } = this.props let {
schema, getComponent, authSelectors, errSelectors, name, specSelectors
} = this.props
const Input = getComponent("Input") const Input = getComponent("Input")
const Row = getComponent("Row") const Row = getComponent("Row")
const Col = getComponent("Col") const Col = getComponent("Col")
@@ -92,6 +90,14 @@ export default class Oauth2 extends React.Component {
const JumpToPath = getComponent("JumpToPath", true) const JumpToPath = getComponent("JumpToPath", true)
const Markdown = getComponent( "Markdown" ) const Markdown = getComponent( "Markdown" )
const { isOAS3 } = specSelectors
// Auth type consts
const IMPLICIT = "implicit"
const PASSWORD = "password"
const ACCESS_CODE = isOAS3() ? "authorizationCode" : "accessCode"
const APPLICATION = isOAS3() ? "clientCredentials" : "application"
let flow = schema.get("flow") let flow = schema.get("flow")
let scopes = schema.get("allowedScopes") || schema.get("scopes") let scopes = schema.get("allowedScopes") || schema.get("scopes")
let authorizedAuth = authSelectors.authorized().get(name) let authorizedAuth = authSelectors.authorized().get(name)
@@ -102,7 +108,7 @@ export default class Oauth2 extends React.Component {
return ( return (
<div> <div>
<h4>OAuth2.0 <JumpToPath path={[ "securityDefinitions", name ]} /></h4> <h4>{name} (OAuth2, { schema.get("flow") }) <JumpToPath path={[ "securityDefinitions", name ]} /></h4>
{ !this.state.appName ? null : <h5>Application: { this.state.appName } </h5> } { !this.state.appName ? null : <h5>Application: { this.state.appName } </h5> }
{ description && <Markdown source={ schema.get("description") } /> } { description && <Markdown source={ schema.get("description") } /> }

View File

@@ -2,6 +2,7 @@ import React from "react"
import PropTypes from "prop-types" import PropTypes from "prop-types"
import { fromJS } from "immutable" import { fromJS } from "immutable"
import ImPropTypes from "react-immutable-proptypes" import ImPropTypes from "react-immutable-proptypes"
import { sanitizeUrl } from "core/utils"
class Path extends React.Component { class Path extends React.Component {
@@ -35,9 +36,9 @@ class Contact extends React.Component {
return ( return (
<div> <div>
{ url && <div><a href={ url } target="_blank">{ name } - Website</a></div> } { url && <div><a href={ sanitizeUrl(url) } target="_blank">{ name } - Website</a></div> }
{ email && { email &&
<a href={`mailto:${email}`}> <a href={sanitizeUrl(`mailto:${email}`)}>
{ url ? `Send email to ${name}` : `Contact ${name}`} { url ? `Send email to ${name}` : `Contact ${name}`}
</a> </a>
} }
@@ -59,7 +60,7 @@ class License extends React.Component {
return ( return (
<div> <div>
{ {
url ? <a target="_blank" href={ url }>{ name }</a> url ? <a target="_blank" href={ sanitizeUrl(url) }>{ name }</a>
: <span>{ name }</span> : <span>{ name }</span>
} }
</div> </div>
@@ -97,7 +98,7 @@ export default class Info extends React.Component {
{ version && <VersionStamp version={version}></VersionStamp> } { version && <VersionStamp version={version}></VersionStamp> }
</h2> </h2>
{ host || basePath ? <Path host={ host } basePath={ basePath } /> : null } { host || basePath ? <Path host={ host } basePath={ basePath } /> : null }
{ url && <a target="_blank" href={ url }><span className="url"> { url } </span></a> } { url && <a target="_blank" href={ sanitizeUrl(url) }><span className="url"> { url } </span></a> }
</hgroup> </hgroup>
<div className="description"> <div className="description">
@@ -106,14 +107,14 @@ export default class Info extends React.Component {
{ {
termsOfService && <div> termsOfService && <div>
<a target="_blank" href={ termsOfService }>Terms of service</a> <a target="_blank" href={ sanitizeUrl(termsOfService) }>Terms of service</a>
</div> </div>
} }
{ contact && contact.size ? <Contact data={ contact } /> : null } { contact && contact.size ? <Contact data={ contact } /> : null }
{ license && license.size ? <License license={ license } /> : null } { license && license.size ? <License license={ license } /> : null }
{ externalDocsUrl ? { externalDocsUrl ?
<a target="_blank" href={externalDocsUrl}>{externalDocsDescription || externalDocsUrl}</a> <a target="_blank" href={sanitizeUrl(externalDocsUrl)}>{externalDocsDescription || externalDocsUrl}</a>
: null } : null }
</div> </div>

View File

@@ -1,5 +1,6 @@
import React from "react" import React from "react"
import PropTypes from "prop-types" import PropTypes from "prop-types"
import { sanitizeUrl } from "core/utils"
export default class OnlineValidatorBadge extends React.Component { export default class OnlineValidatorBadge extends React.Component {
static propTypes = { static propTypes = {
@@ -32,6 +33,8 @@ export default class OnlineValidatorBadge extends React.Component {
let { getConfigs } = this.props let { getConfigs } = this.props
let { spec } = getConfigs() let { spec } = getConfigs()
let sanitizedValidatorUrl = sanitizeUrl(this.state.validatorUrl)
if ( typeof spec === "object" && Object.keys(spec).length) return null if ( typeof spec === "object" && Object.keys(spec).length) return null
if (!this.state.url || !this.state.validatorUrl || this.state.url.indexOf("localhost") >= 0 if (!this.state.url || !this.state.validatorUrl || this.state.url.indexOf("localhost") >= 0
@@ -40,8 +43,8 @@ export default class OnlineValidatorBadge extends React.Component {
} }
return (<span style={{ float: "right"}}> return (<span style={{ float: "right"}}>
<a target="_blank" href={`${ this.state.validatorUrl }/debug?url=${ this.state.url }`}> <a target="_blank" href={`${ sanitizedValidatorUrl }/debug?url=${ this.state.url }`}>
<ValidatorImage src={`${ this.state.validatorUrl }?url=${ this.state.url }`} alt="Online validator badge"/> <ValidatorImage src={`${ sanitizedValidatorUrl }?url=${ this.state.url }`} alt="Online validator badge"/>
</a> </a>
</span>) </span>)
} }

View File

@@ -2,6 +2,7 @@ import React, { PureComponent } from "react"
import PropTypes from "prop-types" import PropTypes from "prop-types"
import { getList } from "core/utils" import { getList } from "core/utils"
import * as CustomPropTypes from "core/proptypes" import * as CustomPropTypes from "core/proptypes"
import { sanitizeUrl } from "core/utils"
//import "less/opblock" //import "less/opblock"
@@ -206,7 +207,7 @@ export default class Operation extends PureComponent {
<span className="opblock-external-docs__description"> <span className="opblock-external-docs__description">
<Markdown source={ externalDocs.get("description") } /> <Markdown source={ externalDocs.get("description") } />
</span> </span>
<a className="opblock-external-docs__link" href={ externalDocs.get("url") }>{ externalDocs.get("url") }</a> <a className="opblock-external-docs__link" href={ sanitizeUrl(externalDocs.get("url")) }>{ externalDocs.get("url") }</a>
</div> </div>
</div> : null </div> : null
} }

View File

@@ -1,7 +1,7 @@
import React from "react" import React from "react"
import PropTypes from "prop-types" import PropTypes from "prop-types"
import { helpers } from "swagger-client" import { helpers } from "swagger-client"
import { createDeepLinkPath } from "core/utils" import { createDeepLinkPath, sanitizeUrl } from "core/utils"
const { opId } = helpers const { opId } = helpers
export default class Operations extends React.Component { export default class Operations extends React.Component {
@@ -101,7 +101,7 @@ export default class Operations extends React.Component {
{ tagExternalDocsUrl ? ": " : null } { tagExternalDocsUrl ? ": " : null }
{ tagExternalDocsUrl ? { tagExternalDocsUrl ?
<a <a
href={tagExternalDocsUrl} href={sanitizeUrl(tagExternalDocsUrl)}
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
target={"_blank"} target={"_blank"}
>{tagExternalDocsUrl}</a> : null >{tagExternalDocsUrl}</a> : null

View File

@@ -1,4 +1,5 @@
import React, { Component } from "react" import React, { Component } from "react"
import { Map } from "immutable"
import PropTypes from "prop-types" import PropTypes from "prop-types"
import win from "core/window" import win from "core/window"
@@ -29,11 +30,21 @@ export default class ParameterRow extends Component {
componentWillReceiveProps(props) { componentWillReceiveProps(props) {
let { specSelectors, pathMethod, param } = props let { specSelectors, pathMethod, param } = props
let { isOAS3 } = specSelectors
let example = param.get("example") let example = param.get("example")
let defaultValue = param.get("default") let defaultValue = param.get("default")
let parameter = specSelectors.getParameter(pathMethod, param.get("name"), param.get("in")) let parameter = specSelectors.getParameter(pathMethod, param.get("name"), param.get("in"))
let enumValue
if(isOAS3()) {
let schema = param.get("schema") || Map()
enumValue = schema.get("enum")
} else {
enumValue = parameter ? parameter.get("enum") : undefined
}
let paramValue = parameter ? parameter.get("value") : undefined let paramValue = parameter ? parameter.get("value") : undefined
let enumValue = parameter ? parameter.get("enum") : undefined
let value let value
if ( paramValue !== undefined ) { if ( paramValue !== undefined ) {
@@ -115,7 +126,7 @@ export default class ParameterRow extends Component {
required={ required } required={ required }
description={param.get("description") ? `${param.get("name")} - ${param.get("description")}` : `${param.get("name")}`} description={param.get("description") ? `${param.get("name")} - ${param.get("description")}` : `${param.get("name")}`}
onChange={ this.onChangeWrapper } onChange={ this.onChangeWrapper }
schema={ param }/> schema={ isOAS3 && isOAS3() ? param.get("schema") : param }/>
} }

View File

@@ -31,6 +31,7 @@ export default Markdown
const sanitizeOptions = { const sanitizeOptions = {
allowedTags: sanitize.defaults.allowedTags.concat([ "h1", "h2", "img" ]), allowedTags: sanitize.defaults.allowedTags.concat([ "h1", "h2", "img" ]),
allowedAttributes: { allowedAttributes: {
...sanitize.defaults.allowedAttributes,
"img": sanitize.defaults.allowedAttributes.img.concat(["title"]) "img": sanitize.defaults.allowedAttributes.img.concat(["title"])
}, },
textFilter: function(text) { textFilter: function(text) {

View File

@@ -22,6 +22,16 @@ export default function authorize ( { auth, authActions, errActions, configs, au
case "implicit": case "implicit":
query.push("response_type=token") query.push("response_type=token")
break break
case "clientCredentials":
// OAS3
authActions.authorizeApplication(auth)
return
case "authorizationCode":
// OAS3
query.push("response_type=code")
break
} }
if (typeof clientId === "string") { if (typeof clientId === "string") {

View File

@@ -22,7 +22,7 @@ export default {
securities.entrySeq().forEach( ([ key, security ]) => { securities.entrySeq().forEach( ([ key, security ]) => {
let type = security.getIn(["schema", "type"]) let type = security.getIn(["schema", "type"])
if ( type === "apiKey" ) { if ( type === "apiKey" || type === "http" ) {
map = map.set(key, security) map = map.set(key, security)
} else if ( type === "basic" ) { } else if ( type === "basic" ) {
let username = security.getIn(["value", "username"]) let username = security.getIn(["value", "username"])

View File

@@ -10,7 +10,7 @@ export const shownDefinitions = createSelector(
export const definitionsToAuthorize = createSelector( export const definitionsToAuthorize = createSelector(
state, state,
() =>( { specSelectors } ) => { () => ( { specSelectors } ) => {
let definitions = specSelectors.securityDefinitions() let definitions = specSelectors.securityDefinitions()
let list = List() let list = List()
@@ -27,7 +27,7 @@ export const definitionsToAuthorize = createSelector(
) )
export const getDefinitionsByNames = ( state, securities ) =>( { specSelectors } ) => { export const getDefinitionsByNames = ( state, securities ) => ( { specSelectors } ) => {
let securityDefinitions = specSelectors.securityDefinitions() let securityDefinitions = specSelectors.securityDefinitions()
let result = List() let result = List()
@@ -64,7 +64,7 @@ export const authorized = createSelector(
) )
export const isAuthorized = ( state, securities ) =>( { authSelectors } ) => { export const isAuthorized = ( state, securities ) => ( { authSelectors } ) => {
let authorized = authSelectors.authorized() let authorized = authSelectors.authorized()
if(!List.isList(securities)) { if(!List.isList(securities)) {

View File

@@ -1,29 +1,60 @@
import { createSelector } from "reselect" import { createSelector } from "reselect"
import { List } from "immutable" import { List, Map, fromJS } from "immutable"
import { isOAS3 as isOAS3Helper } from "../helpers" import { isOAS3 as isOAS3Helper } from "../helpers"
// Helpers // Helpers
const state = state => state
function onlyOAS3(selector) { function onlyOAS3(selector) {
return (ori, system) => (...args) => { return (ori, system) => (state, ...args) => {
const spec = system.getSystem().specSelectors.specJson() const spec = system.getSystem().specSelectors.specJson()
if(isOAS3Helper(spec)) { if(isOAS3Helper(spec)) {
return selector(...args) return selector(system, ...args)
} else { } else {
return ori(...args) return ori(...args)
} }
} }
} }
const nullSelector = createSelector(() => null) export const definitionsToAuthorize = onlyOAS3(createSelector(
state,
({specSelectors}) => specSelectors.securityDefinitions(),
(system, definitions) => {
// Coerce our OpenAPI 3.0 definitions into monoflow definitions
// that look like Swagger2 definitions.
let list = List()
const OAS3NullSelector = onlyOAS3(nullSelector) definitions.entrySeq().forEach( ([ defName, definition ]) => {
const type = definition.get("type")
// Hasta la vista, authentication! if(type === "oauth2") {
export const shownDefinitions = OAS3NullSelector definition.get("flows").entrySeq().forEach(([flowKey, flowVal]) => {
export const definitionsToAuthorize = OAS3NullSelector let translatedDef = fromJS({
export const getDefinitionsByNames = OAS3NullSelector flow: flowKey,
export const authorized = onlyOAS3(() => List()) authorizationUrl: flowVal.get("authorizationUrl"),
export const isAuthorized = OAS3NullSelector tokenUrl: flowVal.get("tokenUrl"),
export const getConfigs = OAS3NullSelector scopes: flowVal.get("scopes"),
type: definition.get("type")
})
list = list.push(new Map({
[defName]: translatedDef.filter((v) => {
// filter out unset values, sometimes `authorizationUrl`
// and `tokenUrl` come out as `undefined` in the data
return v !== undefined
})
}))
})
}
if(type === "http" || type === "apiKey") {
list = list.push(new Map({
[defName]: definition
}))
}
})
return list
}
))

View File

@@ -0,0 +1,134 @@
import React from "react"
import PropTypes from "prop-types"
export default class HttpAuth extends React.Component {
static propTypes = {
authorized: PropTypes.object,
getComponent: PropTypes.func.isRequired,
errSelectors: PropTypes.object.isRequired,
schema: PropTypes.object.isRequired,
name: PropTypes.string.isRequired,
onChange: PropTypes.func
}
constructor(props, context) {
super(props, context)
let { name, schema } = this.props
let value = this.getValue()
this.state = {
name: name,
schema: schema,
value: value
}
}
getValue () {
let { name, authorized } = this.props
return authorized && authorized.getIn([name, "value"])
}
onChange =(e) => {
let { onChange } = this.props
let { value, name } = e.target
let newValue = this.state.value || {}
if(name) {
newValue[name] = value
} else {
newValue = value
}
this.setState({ value: newValue }, () => onChange(this.state))
}
render() {
let { schema, getComponent, errSelectors, name } = this.props
const Input = getComponent("Input")
const Row = getComponent("Row")
const Col = getComponent("Col")
const AuthError = getComponent("authError")
const Markdown = getComponent( "Markdown" )
const JumpToPath = getComponent("JumpToPath", true)
const scheme = schema.get("scheme")
let value = this.getValue()
let errors = errSelectors.allErrors().filter( err => err.get("authId") === name)
if(scheme === "basic") {
let username = value ? value.get("username") : null
return <div>
<h4>
<code>{ name || schema.get("name") }</code>&nbsp;
(http, Basic)
<JumpToPath path={[ "securityDefinitions", name ]} />
</h4>
{ username && <h6>Authorized</h6> }
<Row>
<Markdown source={ schema.get("description") } />
</Row>
<Row>
<label>Username:</label>
{
username ? <code> { username } </code>
: <Col><Input type="text" required="required" name="username" onChange={ this.onChange }/></Col>
}
</Row>
<Row>
<label>Password:</label>
{
username ? <code> ****** </code>
: <Col><Input required="required"
autoComplete="new-password"
name="password"
type="password"
onChange={ this.onChange }/></Col>
}
</Row>
{
errors.valueSeq().map( (error, key) => {
return <AuthError error={ error }
key={ key }/>
} )
}
</div>
}
if(scheme === "bearer") {
return (
<div>
<h4>
<code>{ name || schema.get("name") }</code>&nbsp;
(http, Bearer)
<JumpToPath path={[ "securityDefinitions", name ]} />
</h4>
{ value && <h6>Authorized</h6>}
<Row>
<Markdown source={ schema.get("description") } />
</Row>
<Row>
<p>In: <code>{ schema.get("in") }</code></p>
</Row>
<Row>
<label>Value:</label>
{
value ? <code> ****** </code>
: <Col><Input type="text" onChange={ this.onChange }/></Col>
}
</Row>
{
errors.valueSeq().map( (error, key) => {
return <AuthError error={ error }
key={ key }/>
} )
}
</div>
)
}
return <div>
<em><b>{name}</b> HTTP authentication: unsupported or missing scheme</em>
</div>
}
}

View File

@@ -3,9 +3,11 @@ import RequestBody from "./request-body"
import OperationLink from "./operation-link.jsx" import OperationLink from "./operation-link.jsx"
import Servers from "./servers" import Servers from "./servers"
import RequestBodyEditor from "./request-body-editor" import RequestBodyEditor from "./request-body-editor"
import HttpAuth from "./http-auth"
export default { export default {
Callbacks, Callbacks,
HttpAuth,
RequestBody, RequestBody,
Servers, Servers,
RequestBodyEditor, RequestBodyEditor,

View File

@@ -48,12 +48,16 @@ export const definitions = onlyOAS3(createSelector(
spec => spec.getIn(["components", "schemas"]) || Map() spec => spec.getIn(["components", "schemas"]) || Map()
)) ))
export const securityDefinitions = onlyOAS3(createSelector(
spec,
spec => spec.getIn(["components", "securitySchemes"]) || null
))
export const host = OAS3NullSelector export const host = OAS3NullSelector
export const basePath = OAS3NullSelector export const basePath = OAS3NullSelector
export const consumes = OAS3NullSelector export const consumes = OAS3NullSelector
export const produces = OAS3NullSelector export const produces = OAS3NullSelector
export const schemes = OAS3NullSelector export const schemes = OAS3NullSelector
export const securityDefinitions = OAS3NullSelector
// New selectors // New selectors

View File

@@ -0,0 +1,23 @@
import React from "react"
import { OAS3ComponentWrapFactory } from "../helpers"
export default OAS3ComponentWrapFactory(({ Ori, ...props }) => {
const {
schema, getComponent, errSelectors, authorized, onAuthChange, name
} = props
const HttpAuth = getComponent("HttpAuth")
const type = schema.get("type")
if(type === "http") {
return <HttpAuth key={ name }
schema={ schema }
name={ name }
errSelectors={ errSelectors }
authorized={ authorized }
getComponent={ getComponent }
onChange={ onAuthChange }/>
} else {
return <Ori {...props} />
}
})

View File

@@ -1,4 +1,5 @@
import Markdown from "./markdown" import Markdown from "./markdown"
import AuthItem from "./auth-item"
import parameters from "./parameters" import parameters from "./parameters"
import VersionStamp from "./version-stamp" import VersionStamp from "./version-stamp"
import OnlineValidatorBadge from "./online-validator-badge" import OnlineValidatorBadge from "./online-validator-badge"
@@ -6,6 +7,7 @@ import Model from "./model"
export default { export default {
Markdown, Markdown,
AuthItem,
parameters, parameters,
VersionStamp, VersionStamp,
model: Model, model: Model,

View File

@@ -80,7 +80,12 @@ export const parseToJson = (str) => ({specActions, specSelectors, errActions}) =
} }
export const resolveSpec = (json, url) => ({specActions, specSelectors, errActions, fn: { fetch, resolve, AST }, getConfigs}) => { export const resolveSpec = (json, url) => ({specActions, specSelectors, errActions, fn: { fetch, resolve, AST }, getConfigs}) => {
const { modelPropertyMacro, parameterMacro } = getConfigs() const {
modelPropertyMacro,
parameterMacro,
requestInterceptor,
responseInterceptor
} = getConfigs()
if(typeof(json) === "undefined") { if(typeof(json) === "undefined") {
json = specSelectors.specJson() json = specSelectors.specJson()
@@ -93,8 +98,15 @@ export const resolveSpec = (json, url) => ({specActions, specSelectors, errActio
let specStr = specSelectors.specStr() let specStr = specSelectors.specStr()
return resolve({fetch, spec: json, baseDoc: url, modelPropertyMacro, parameterMacro }) return resolve({
.then( ({spec, errors}) => { fetch,
spec: json,
baseDoc: url,
modelPropertyMacro,
parameterMacro,
requestInterceptor,
responseInterceptor
}).then( ({spec, errors}) => {
errActions.clear({ errActions.clear({
type: "thrown" type: "thrown"
}) })
@@ -137,10 +149,13 @@ export function changeParam( path, paramName, paramIn, value, isXml ){
} }
} }
export function validateParams( payload ){ export const validateParams = ( payload, isOAS3 ) =>{
return { return {
type: VALIDATE_PARAMS, type: VALIDATE_PARAMS,
payload:{ pathMethod: payload } payload:{
pathMethod: payload,
isOAS3
}
} }
} }

View File

@@ -51,14 +51,14 @@ export default {
}) })
}, },
[VALIDATE_PARAMS]: ( state, { payload: { pathMethod } } ) => { [VALIDATE_PARAMS]: ( state, { payload: { pathMethod, isOAS3 } } ) => {
let operation = state.getIn( [ "resolved", "paths", ...pathMethod ] ) let operation = state.getIn( [ "resolved", "paths", ...pathMethod ] )
let isXml = /xml/i.test(operation.get("consumes_value")) let isXml = /xml/i.test(operation.get("consumes_value"))
return state.updateIn( [ "resolved", "paths", ...pathMethod, "parameters" ], fromJS([]), parameters => { return state.updateIn( [ "resolved", "paths", ...pathMethod, "parameters" ], fromJS([]), parameters => {
return parameters.withMutations( parameters => { return parameters.withMutations( parameters => {
for ( let i = 0, len = parameters.count(); i < len; i++ ) { for ( let i = 0, len = parameters.count(); i < len; i++ ) {
let errors = validateParam(parameters.get(i), isXml) let errors = validateParam(parameters.get(i), isXml, isOAS3)
parameters.setIn([i, "errors"], fromJS(errors)) parameters.setIn([i, "errors"], fromJS(errors))
} }
}) })

View File

@@ -13,3 +13,7 @@ export const executeRequest = (ori, { specActions }) => (req) => {
specActions.logRequest(req) specActions.logRequest(req)
return ori(req) return ori(req)
} }
export const validateParams = (ori, { specSelectors }) => (req) => {
return ori(req, specSelectors.isOAS3())
}

View File

@@ -17,6 +17,7 @@ import AuthorizationPopup from "core/components/auth/authorization-popup"
import AuthorizeBtn from "core/components/auth/authorize-btn" import AuthorizeBtn from "core/components/auth/authorize-btn"
import AuthorizeOperationBtn from "core/components/auth/authorize-operation-btn" import AuthorizeOperationBtn from "core/components/auth/authorize-operation-btn"
import Auths from "core/components/auth/auths" import Auths from "core/components/auth/auths"
import AuthItem from "core/components/auth/auth-item"
import AuthError from "core/components/auth/error" import AuthError from "core/components/auth/error"
import ApiKeyAuth from "core/components/auth/api-key-auth" import ApiKeyAuth from "core/components/auth/api-key-auth"
import BasicAuth from "core/components/auth/basic-auth" import BasicAuth from "core/components/auth/basic-auth"
@@ -70,6 +71,7 @@ export default function() {
authorizeBtn: AuthorizeBtn, authorizeBtn: AuthorizeBtn,
authorizeOperationBtn: AuthorizeOperationBtn, authorizeOperationBtn: AuthorizeOperationBtn,
auths: Auths, auths: Auths,
AuthItem: AuthItem,
authError: AuthError, authError: AuthError,
oauth2: Oauth2, oauth2: Oauth2,
apiKeyAuth: ApiKeyAuth, apiKeyAuth: ApiKeyAuth,

View File

@@ -1,5 +1,5 @@
import Im from "immutable" import Im from "immutable"
import { sanitizeUrl as braintreeSanitizeUrl } from "@braintree/sanitize-url"
import camelCase from "lodash/camelCase" import camelCase from "lodash/camelCase"
import upperFirst from "lodash/upperFirst" import upperFirst from "lodash/upperFirst"
import _memoize from "lodash/memoize" import _memoize from "lodash/memoize"
@@ -537,16 +537,18 @@ export const validateMinLength = (val, min) => {
} }
// validation of parameters before execute // validation of parameters before execute
export const validateParam = (param, isXml) => { export const validateParam = (param, isXml, isOAS3 = false) => {
let errors = [] let errors = []
let value = isXml && param.get("in") === "body" ? param.get("value_xml") : param.get("value") let value = isXml && param.get("in") === "body" ? param.get("value_xml") : param.get("value")
let required = param.get("required") let required = param.get("required")
let maximum = param.get("maximum")
let minimum = param.get("minimum") let paramDetails = isOAS3 ? param.get("schema") : param
let type = param.get("type") let maximum = paramDetails.get("maximum")
let format = param.get("format") let minimum = paramDetails.get("minimum")
let maxLength = param.get("maxLength") let type = paramDetails.get("type")
let minLength = param.get("minLength") let format = paramDetails.get("format")
let maxLength = paramDetails.get("maxLength")
let minLength = paramDetails.get("minLength")
/* /*
If the parameter is required OR the parameter has a value (meaning optional, but filled in) If the parameter is required OR the parameter has a value (meaning optional, but filled in)
@@ -616,7 +618,7 @@ export const validateParam = (param, isXml) => {
if ( !value.count() ) { return errors } if ( !value.count() ) { return errors }
itemType = param.getIn(["items", "type"]) itemType = paramDetails.getIn(["items", "type"])
value.forEach((item, index) => { value.forEach((item, index) => {
let err let err
@@ -720,6 +722,14 @@ export const shallowEqualKeys = (a,b, keys) => {
}) })
} }
export function sanitizeUrl(url) {
if(typeof url !== "string" || url === "") {
return ""
}
return braintreeSanitizeUrl(url)
}
export function getAcceptControllingResponse(responses) { export function getAcceptControllingResponse(responses) {
if(!Im.OrderedMap.isOrderedMap(responses)) { if(!Im.OrderedMap.isOrderedMap(responses)) {
// wrong type! // wrong type!

View File

@@ -1,6 +1,6 @@
# Add a plugin # Add a plugin
### Swagger-UX relies on plugins for all the good stuff. ### Swagger-UI relies on plugins for all the good stuff.
Plugins allow you to add Plugins allow you to add
- `statePlugins` - `statePlugins`

View File

@@ -129,7 +129,7 @@ export default class Topbar extends React.Component {
<div className="topbar"> <div className="topbar">
<div className="wrapper"> <div className="wrapper">
<div className="topbar-wrapper"> <div className="topbar-wrapper">
<Link href="#" title="Swagger UX"> <Link href="#">
<img height="30" width="30" src={ Logo } alt="Swagger UI"/> <img height="30" width="30" src={ Logo } alt="Swagger UI"/>
<span>swagger</span> <span>swagger</span>
</Link> </Link>

View File

@@ -30,6 +30,10 @@ select
.opblock-body select .opblock-body select
{ {
min-width: 230px; min-width: 230px;
@media (max-width: 768px)
{
min-width: 180px;
}
} }
label label
@@ -56,6 +60,10 @@ input[type=file]
border: 1px solid #d9d9d9; border: 1px solid #d9d9d9;
border-radius: 4px; border-radius: 4px;
background: #fff; background: #fff;
@media (max-width: 768px) {
max-width: 175px;
}
&.invalid &.invalid
{ {

View File

@@ -250,6 +250,10 @@
.opblock-summary-path__deprecated .opblock-summary-path__deprecated
{ {
font-size: 16px; font-size: 16px;
@media (max-width: 768px) {
font-size: 12px;
}
display: flex; display: flex;
flex: 0 3 auto; flex: 0 3 auto;
@@ -768,14 +772,6 @@
} }
} }
.renderedMarkdown {
p {
@include text_body();
margin-top: 0px;
margin-bottom: 0px;
}
}
.response-content-type { .response-content-type {
padding-top: 1em; padding-top: 1em;

View File

@@ -1,6 +1,6 @@
.topbar .topbar
{ {
padding: 8px 30px; padding: 8px 0;
background-color: #89bf04; background-color: #89bf04;
.topbar-wrapper .topbar-wrapper
@@ -39,7 +39,6 @@
input[type=text] input[type=text]
{ {
width: 100%; width: 100%;
min-width: 350px;
margin: 0; margin: 0;
border: 2px solid #547f00; border: 2px solid #547f00;
@@ -84,7 +83,7 @@
font-size: 16px; font-size: 16px;
font-weight: bold; font-weight: bold;
padding: 4px 40px; padding: 4px 30px;
border: none; border: none;
border-radius: 0 4px 4px 0; border-radius: 0 4px 4px 0;

View File

@@ -24,6 +24,12 @@ describe("Markdown component", function() {
const el = render(<Markdown source={str} />) const el = render(<Markdown source={str} />)
expect(el.html()).toEqual(`<div class="markdown"><h1>h1</h1>\n<h2>h2</h2>\n<h3>h3</h3>\n<h4>h4</h4>\n<h5>h5</h5>\n<h6>h6</h6>\n</div>`) expect(el.html()).toEqual(`<div class="markdown"><h1>h1</h1>\n<h2>h2</h2>\n<h3>h3</h3>\n<h4>h4</h4>\n<h5>h5</h5>\n<h6>h6</h6>\n</div>`)
}) })
it("allows links", function() {
const str = `[Link](https://example.com/)`
const el = render(<Markdown source={str} />)
expect(el.html()).toEqual(`<div class="markdown"><p><a href="https://example.com/" target="_blank">Link</a></p>\n</div>`)
})
}) })
describe("OAS 3", function() { describe("OAS 3", function() {

View File

@@ -0,0 +1,116 @@
/* eslint-env mocha */
import expect, { createSpy } from "expect"
import { Map, fromJS } from "immutable"
import {
definitionsToAuthorize
} from "corePlugins/oas3/auth-extensions/wrap-selectors"
describe("oas3 plugin - auth extensions - wrapSelectors", function(){
describe("execute", function(){
it("should add `securities` to the oriAction call", function(){
// Given
const system = {
getSystem: () => system,
specSelectors: {
specJson: () => fromJS({
openapi: "3.0.0"
}),
securityDefinitions: () => {
return fromJS({
"oauth2AuthorizationCode": {
"type": "oauth2",
"flows": {
"authorizationCode": {
"authorizationUrl": "http://google.com/",
"tokenUrl": "http://google.com/",
"scopes": {
"myScope": "our only scope"
}
}
}
},
"oauth2Multiflow": {
"type": "oauth2",
"flows": {
"clientCredentials": {
"tokenUrl": "http://google.com/",
"scopes": {
"myScope": "our only scope"
}
},
"password": {
"tokenUrl": "http://google.com/",
"scopes": {
"myScope": "our only scope"
}
},
"authorizationCode": {
"authorizationUrl": "http://google.com/",
"tokenUrl": "http://google.com/",
"scopes": {
"myScope": "our only scope"
}
}
}
}
})
}
}
}
// When
let res = definitionsToAuthorize(() => null, system)()
// Then
expect(res.toJS()).toEqual([
{
oauth2AuthorizationCode: {
flow: "authorizationCode",
authorizationUrl: "http://google.com/",
tokenUrl: "http://google.com/",
scopes: {
"myScope": "our only scope"
},
type: "oauth2"
}
},
{
oauth2Multiflow: {
flow: "clientCredentials",
tokenUrl: "http://google.com/",
scopes: {
"myScope": "our only scope"
},
type: "oauth2"
}
},
{
oauth2Multiflow: {
flow: "password",
tokenUrl: "http://google.com/",
scopes: {
"myScope": "our only scope"
},
type: "oauth2"
}
},
{
oauth2Multiflow: {
flow: "authorizationCode",
authorizationUrl: "http://google.com/",
tokenUrl: "http://google.com/",
scopes: {
"myScope": "our only scope"
},
type: "oauth2"
}
},
])
})
})
})

View File

@@ -16,7 +16,8 @@ import {
fromJSOrdered, fromJSOrdered,
getAcceptControllingResponse, getAcceptControllingResponse,
createDeepLinkPath, createDeepLinkPath,
escapeDeepLinkPath escapeDeepLinkPath,
sanitizeUrl
} from "core/utils" } from "core/utils"
import win from "core/window" import win from "core/window"
@@ -277,461 +278,438 @@ describe("utils", function() {
let param = null let param = null
let result = null let result = null
it("skips validation when `type` is not specified", function() { const assertValidateParam = (param, expectedError) => {
// invalid type // Swagger 2.0 version
result = validateParam( fromJS(param), false )
expect( result ).toEqual( expectedError )
// OAS3 version, using `schema` sub-object
let oas3Param = {
value: param.value,
required: param.required,
schema: {
...param,
value: undefined,
required: undefined
}
}
result = validateParam( fromJS(oas3Param), false, true )
expect( result ).toEqual( expectedError )
}
it("should check the isOAS3 flag when validating parameters", function() {
// This should "skip" validation because there is no `schema.type` property
// and we are telling `validateParam` this is an OAS3 spec
param = fromJS({ param = fromJS({
required: false, value: "",
type: undefined, required: true,
value: "" schema: {
notTheTypeProperty: "string"
}
}) })
result = validateParam( param, false ) result = validateParam( param, false, true )
expect( result ).toEqual( [] ) expect( result ).toEqual( [] )
}) })
it("validates required strings", function() { it("validates required strings", function() {
// invalid string // invalid string
param = fromJS({ param = {
required: true, required: true,
type: "string", type: "string",
value: "" value: ""
}) }
result = validateParam( param, false ) assertValidateParam(param, ["Required field is not provided"])
expect( result ).toEqual( ["Required field is not provided"] )
// valid string // valid string
param = fromJS({ param = {
required: true, required: true,
type: "string", type: "string",
value: "test string" value: "test string"
}) }
result = validateParam( param, false ) assertValidateParam(param, [])
expect( result ).toEqual( [] )
// valid string with min and max length // valid string with min and max length
param = fromJS({ param = {
required: true, required: true,
type: "string", type: "string",
value: "test string", value: "test string",
maxLength: 50, maxLength: 50,
minLength: 1 minLength: 1
}) }
result = validateParam( param, false ) assertValidateParam(param, [])
expect( result ).toEqual( [] )
}) })
it("validates required strings with min and max length", function() { it("validates required strings with min and max length", function() {
// invalid string with max length // invalid string with max length
param = fromJS({ param = {
required: true, required: true,
type: "string", type: "string",
value: "test string", value: "test string",
maxLength: 5 maxLength: 5
}) }
result = validateParam( param, false ) assertValidateParam(param, ["Value must be less than MaxLength"])
expect( result ).toEqual( ["Value must be less than MaxLength"] )
// invalid string with max length 0 // invalid string with max length 0
param = fromJS({ param = {
required: true, required: true,
type: "string", type: "string",
value: "test string", value: "test string",
maxLength: 0 maxLength: 0
}) }
result = validateParam( param, false ) assertValidateParam(param, ["Value must be less than MaxLength"])
expect( result ).toEqual( ["Value must be less than MaxLength"] )
// invalid string with min length // invalid string with min length
param = fromJS({ param = {
required: true, required: true,
type: "string", type: "string",
value: "test string", value: "test string",
minLength: 50 minLength: 50
}) }
result = validateParam( param, false ) assertValidateParam(param, ["Value must be greater than MinLength"])
expect( result ).toEqual( ["Value must be greater than MinLength"] )
}) })
it("validates optional strings", function() { it("validates optional strings", function() {
// valid (empty) string // valid (empty) string
param = fromJS({ param = {
required: false, required: false,
type: "string", type: "string",
value: "" value: ""
}) }
result = validateParam( param, false ) assertValidateParam(param, [])
expect( result ).toEqual( [] )
// valid string // valid string
param = fromJS({ param = {
required: false, required: false,
type: "string", type: "string",
value: "test" value: "test"
}) }
result = validateParam( param, false ) assertValidateParam(param, [])
expect( result ).toEqual( [] )
}) })
it("validates required files", function() { it("validates required files", function() {
// invalid file // invalid file
param = fromJS({ param = {
required: true, required: true,
type: "file", type: "file",
value: undefined value: undefined
}) }
result = validateParam( param, false ) assertValidateParam(param, ["Required field is not provided"])
expect( result ).toEqual( ["Required field is not provided"] )
// valid file // valid file
param = fromJS({ param = {
required: true, required: true,
type: "file", type: "file",
value: new win.File() value: new win.File()
}) }
result = validateParam( param, false ) assertValidateParam(param, [])
expect( result ).toEqual( [] )
}) })
it("validates optional files", function() { it("validates optional files", function() {
// invalid file // invalid file
param = fromJS({ param = {
required: false, required: false,
type: "file", type: "file",
value: "not a file" value: "not a file"
}) }
result = validateParam( param, false ) assertValidateParam(param, ["Value must be a file"])
expect( result ).toEqual( ["Value must be a file"] )
// valid (empty) file // valid (empty) file
param = fromJS({ param = {
required: false, required: false,
type: "file", type: "file",
value: undefined value: undefined
}) }
result = validateParam( param, false ) assertValidateParam(param, [])
expect( result ).toEqual( [] )
// valid file // valid file
param = fromJS({ param = {
required: false, required: false,
type: "file", type: "file",
value: new win.File() value: new win.File()
}) }
result = validateParam( param, false ) assertValidateParam(param, [])
expect( result ).toEqual( [] )
}) })
it("validates required arrays", function() { it("validates required arrays", function() {
// invalid (empty) array // invalid (empty) array
param = fromJS({ param = {
required: true, required: true,
type: "array", type: "array",
value: [] value: []
}) }
result = validateParam( param, false ) assertValidateParam(param, ["Required field is not provided"])
expect( result ).toEqual( ["Required field is not provided"] )
// invalid (not an array) // invalid (not an array)
param = fromJS({ param = {
required: true, required: true,
type: "array", type: "array",
value: undefined value: undefined
}) }
result = validateParam( param, false ) assertValidateParam(param, ["Required field is not provided"])
expect( result ).toEqual( ["Required field is not provided"] )
// invalid array, items do not match correct type // invalid array, items do not match correct type
param = fromJS({ param = {
required: true, required: true,
type: "array", type: "array",
value: [1], value: [1],
items: { items: {
type: "string" type: "string"
} }
}) }
result = validateParam( param, false ) assertValidateParam(param, [{index: 0, error: "Value must be a string"}])
expect( result ).toEqual( [{index: 0, error: "Value must be a string"}] )
// valid array, with no 'type' for items // valid array, with no 'type' for items
param = fromJS({ param = {
required: true, required: true,
type: "array", type: "array",
value: ["1"] value: ["1"]
}) }
result = validateParam( param, false ) assertValidateParam(param, [])
expect( result ).toEqual( [] )
// valid array, items match type // valid array, items match type
param = fromJS({ param = {
required: true, required: true,
type: "array", type: "array",
value: ["1"], value: ["1"],
items: { items: {
type: "string" type: "string"
} }
}) }
result = validateParam( param, false ) assertValidateParam(param, [])
expect( result ).toEqual( [] )
}) })
it("validates optional arrays", function() { it("validates optional arrays", function() {
// valid, empty array // valid, empty array
param = fromJS({ param = {
required: false, required: false,
type: "array", type: "array",
value: [] value: []
}) }
result = validateParam( param, false ) assertValidateParam(param, [])
expect( result ).toEqual( [] )
// invalid, items do not match correct type // invalid, items do not match correct type
param = fromJS({ param = {
required: false, required: false,
type: "array", type: "array",
value: ["number"], value: ["number"],
items: { items: {
type: "number" type: "number"
} }
}) }
result = validateParam( param, false ) assertValidateParam(param, [{index: 0, error: "Value must be a number"}])
expect( result ).toEqual( [{index: 0, error: "Value must be a number"}] )
// valid // valid
param = fromJS({ param = {
required: false, required: false,
type: "array", type: "array",
value: ["test"], value: ["test"],
items: { items: {
type: "string" type: "string"
} }
}) }
result = validateParam( param, false ) assertValidateParam(param, [])
expect( result ).toEqual( [] )
}) })
it("validates required booleans", function() { it("validates required booleans", function() {
// invalid boolean value // invalid boolean value
param = fromJS({ param = {
required: true, required: true,
type: "boolean", type: "boolean",
value: undefined value: undefined
}) }
result = validateParam( param, false ) assertValidateParam(param, ["Required field is not provided"])
expect( result ).toEqual( ["Required field is not provided"] )
// invalid boolean value (not a boolean) // invalid boolean value (not a boolean)
param = fromJS({ param = {
required: true, required: true,
type: "boolean", type: "boolean",
value: "test string" value: "test string"
}) }
result = validateParam( param, false ) assertValidateParam(param, ["Required field is not provided"])
expect( result ).toEqual( ["Required field is not provided"] )
// valid boolean value // valid boolean value
param = fromJS({ param = {
required: true, required: true,
type: "boolean", type: "boolean",
value: "true" value: "true"
}) }
result = validateParam( param, false ) assertValidateParam(param, [])
expect( result ).toEqual( [] )
// valid boolean value // valid boolean value
param = fromJS({ param = {
required: true, required: true,
type: "boolean", type: "boolean",
value: false value: false
}) }
result = validateParam( param, false ) assertValidateParam(param, [])
expect( result ).toEqual( [] )
}) })
it("validates optional booleans", function() { it("validates optional booleans", function() {
// valid (empty) boolean value // valid (empty) boolean value
param = fromJS({ param = {
required: false, required: false,
type: "boolean", type: "boolean",
value: undefined value: undefined
}) }
result = validateParam( param, false ) assertValidateParam(param, [])
expect( result ).toEqual( [] )
// invalid boolean value (not a boolean) // invalid boolean value (not a boolean)
param = fromJS({ param = {
required: false, required: false,
type: "boolean", type: "boolean",
value: "test string" value: "test string"
}) }
result = validateParam( param, false ) assertValidateParam(param, ["Value must be a boolean"])
expect( result ).toEqual( ["Value must be a boolean"] )
// valid boolean value // valid boolean value
param = fromJS({ param = {
required: false, required: false,
type: "boolean", type: "boolean",
value: "true" value: "true"
}) }
result = validateParam( param, false ) assertValidateParam(param, [])
expect( result ).toEqual( [] )
// valid boolean value // valid boolean value
param = fromJS({ param = {
required: false, required: false,
type: "boolean", type: "boolean",
value: false value: false
}) }
result = validateParam( param, false ) assertValidateParam(param, [])
expect( result ).toEqual( [] )
}) })
it("validates required numbers", function() { it("validates required numbers", function() {
// invalid number, string instead of a number // invalid number, string instead of a number
param = fromJS({ param = {
required: true, required: true,
type: "number", type: "number",
value: "test" value: "test"
}) }
result = validateParam( param, false ) assertValidateParam(param, ["Required field is not provided"])
expect( result ).toEqual( ["Required field is not provided"] )
// invalid number, undefined value // invalid number, undefined value
param = fromJS({ param = {
required: true, required: true,
type: "number", type: "number",
value: undefined value: undefined
}) }
result = validateParam( param, false ) assertValidateParam(param, ["Required field is not provided"])
expect( result ).toEqual( ["Required field is not provided"] )
// valid number with min and max // valid number with min and max
param = fromJS({ param = {
required: true, required: true,
type: "number", type: "number",
value: 10, value: 10,
minimum: 5, minimum: 5,
maximum: 99 maximum: 99
}) }
result = validateParam( param, false ) assertValidateParam(param, [])
expect( result ).toEqual( [] )
// valid negative number with min and max // valid negative number with min and max
param = fromJS({ param = {
required: true, required: true,
type: "number", type: "number",
value: -10, value: -10,
minimum: -50, minimum: -50,
maximum: -5 maximum: -5
}) }
result = validateParam( param, false ) assertValidateParam(param, [])
expect( result ).toEqual( [] )
// invalid number with maximum:0 // invalid number with maximum:0
param = fromJS({ param = {
required: true, required: true,
type: "number", type: "number",
value: 1, value: 1,
maximum: 0 maximum: 0
}) }
result = validateParam( param, false ) assertValidateParam(param, ["Value must be less than Maximum"])
expect( result ).toEqual( ["Value must be less than Maximum"] )
// invalid number with minimum:0 // invalid number with minimum:0
param = fromJS({ param = {
required: true, required: true,
type: "number", type: "number",
value: -10, value: -10,
minimum: 0 minimum: 0
}) }
result = validateParam( param, false ) assertValidateParam(param, ["Value must be greater than Minimum"])
expect( result ).toEqual( ["Value must be greater than Minimum"] )
}) })
it("validates optional numbers", function() { it("validates optional numbers", function() {
// invalid number, string instead of a number // invalid number, string instead of a number
param = fromJS({ param = {
required: false, required: false,
type: "number", type: "number",
value: "test" value: "test"
}) }
result = validateParam( param, false ) assertValidateParam(param, ["Value must be a number"])
expect( result ).toEqual( ["Value must be a number"] )
// valid (empty) number // valid (empty) number
param = fromJS({ param = {
required: false, required: false,
type: "number", type: "number",
value: undefined value: undefined
}) }
result = validateParam( param, false ) assertValidateParam(param, [])
expect( result ).toEqual( [] )
// valid number // valid number
param = fromJS({ param = {
required: false, required: false,
type: "number", type: "number",
value: 10 value: 10
}) }
result = validateParam( param, false ) assertValidateParam(param, [])
expect( result ).toEqual( [] )
}) })
it("validates required integers", function() { it("validates required integers", function() {
// invalid integer, string instead of an integer // invalid integer, string instead of an integer
param = fromJS({ param = {
required: true, required: true,
type: "integer", type: "integer",
value: "test" value: "test"
}) }
result = validateParam( param, false ) assertValidateParam(param, ["Required field is not provided"])
expect( result ).toEqual( ["Required field is not provided"] )
// invalid integer, undefined value // invalid integer, undefined value
param = fromJS({ param = {
required: true, required: true,
type: "integer", type: "integer",
value: undefined value: undefined
}) }
result = validateParam( param, false ) assertValidateParam(param, ["Required field is not provided"])
expect( result ).toEqual( ["Required field is not provided"] )
// valid integer // valid integer
param = fromJS({ param = {
required: true, required: true,
type: "integer", type: "integer",
value: 10 value: 10
}) }
result = validateParam( param, false ) assertValidateParam(param, [])
expect( result ).toEqual( [] )
}) })
it("validates optional integers", function() { it("validates optional integers", function() {
// invalid integer, string instead of an integer // invalid integer, string instead of an integer
param = fromJS({ param = {
required: false, required: false,
type: "integer", type: "integer",
value: "test" value: "test"
}) }
result = validateParam( param, false ) assertValidateParam(param, ["Value must be an integer"])
expect( result ).toEqual( ["Value must be an integer"] )
// valid (empty) integer // valid (empty) integer
param = fromJS({ param = {
required: false, required: false,
type: "integer", type: "integer",
value: undefined value: undefined
}) }
result = validateParam( param, false ) assertValidateParam(param, [])
expect( result ).toEqual( [] )
// integers // integers
param = fromJS({ param = {
required: false, required: false,
type: "integer", type: "integer",
value: 10 value: 10
}) }
result = validateParam( param, false ) assertValidateParam(param, [])
expect( result ).toEqual( [] )
}) })
}) })
@@ -908,4 +886,43 @@ describe("utils", function() {
expect(result).toEqual("hello\\#world") expect(result).toEqual("hello\\#world")
}) })
}) })
describe("sanitizeUrl", function() {
it("should sanitize a `javascript:` url", function() {
const res = sanitizeUrl("javascript:alert('bam!')")
expect(res).toEqual("about:blank")
})
it("should sanitize a `data:` url", function() {
const res = sanitizeUrl(`data:text/html;base64,PHNjcmlwdD5hbGVydCgiSGV
sbG8iKTs8L3NjcmlwdD4=`)
expect(res).toEqual("about:blank")
})
it("should not modify a `http:` url", function() {
const res = sanitizeUrl(`http://swagger.io/`)
expect(res).toEqual("http://swagger.io/")
})
it("should not modify a `https:` url", function() {
const res = sanitizeUrl(`https://swagger.io/`)
expect(res).toEqual("https://swagger.io/")
})
it("should gracefully handle empty strings", function() {
expect(sanitizeUrl("")).toEqual("")
})
it("should gracefully handle non-string values", function() {
expect(sanitizeUrl(123)).toEqual("")
expect(sanitizeUrl(null)).toEqual("")
expect(sanitizeUrl(undefined)).toEqual("")
expect(sanitizeUrl([])).toEqual("")
expect(sanitizeUrl({})).toEqual("")
})
})
}) })

View File

@@ -11,7 +11,7 @@ let rules = [
name: "[name].js" name: "[name].js"
} }
}, },
{ loader: "babel-loader" } { loader: "babel-loader?retainLines=true" }
] ]
} }
] ]

View File

@@ -11,7 +11,7 @@ let rules = [
name: "[name].js" name: "[name].js"
} }
}, },
{ loader: "babel-loader" } { loader: "babel-loader?retainLines=true" }
] ]
} }
] ]

View File

@@ -13,7 +13,7 @@ let rules = [
name: "[name].js" name: "[name].js"
} }
}, },
{ loader: "babel-loader" } { loader: "babel-loader?retainLines=true" }
] ]
} }
] ]

View File

@@ -9,13 +9,7 @@ const rules = [
inline: true inline: true
} }
}, },
{ loader: "babel-loader" } { loader: "babel-loader?retainLines=true" }
]
},
{ test: /\.(jsx)(\?.*)?$/,
use: [
{ loader: "react-hot-loader" },
{ loader: "babel-loader" }
] ]
}, },
{ test: /\.(css)(\?.*)?$/, { test: /\.(css)(\?.*)?$/,
@@ -48,7 +42,7 @@ module.exports = require("./make-webpack-config")(rules, {
_special: { _special: {
separateStylesheets: false, separateStylesheets: false,
}, },
devtool: "eval", devtool: "eval-source-map",
entry: { entry: {
"swagger-ui-bundle": [ "swagger-ui-bundle": [
"./src/polyfills", "./src/polyfills",