diff --git a/dev-helpers/oauth2-redirect.html b/dev-helpers/oauth2-redirect.html index a7eb162d10f..eb00dc686a0 100644 --- a/dev-helpers/oauth2-redirect.html +++ b/dev-helpers/oauth2-redirect.html @@ -27,7 +27,10 @@ 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) { oauth2.errCb({ authId: oauth2.auth.name, diff --git a/dist/oauth2-redirect.html b/dist/oauth2-redirect.html index a7eb162d10f..eb00dc686a0 100644 --- a/dist/oauth2-redirect.html +++ b/dist/oauth2-redirect.html @@ -27,7 +27,10 @@ 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) { oauth2.errCb({ authId: oauth2.auth.name, diff --git a/src/core/components/auth/api-key-auth.jsx b/src/core/components/auth/api-key-auth.jsx index d9796f761de..2d8a5f52647 100644 --- a/src/core/components/auth/api-key-auth.jsx +++ b/src/core/components/auth/api-key-auth.jsx @@ -51,14 +51,15 @@ export default class ApiKeyAuth extends React.Component { return (
-

Api key authorization

+

+ { name || schema.get("name") }  + (apiKey) + +

{ value &&
Authorized
} - -

Name: { schema.get("name") }

-

In: { schema.get("in") }

diff --git a/src/core/components/auth/auth-item.jsx b/src/core/components/auth/auth-item.jsx new file mode 100644 index 00000000000..cffc8147539 --- /dev/null +++ b/src/core/components/auth/auth-item.jsx @@ -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 = + break + case "basic": authEl = + break + default: authEl =
Unknown security definition type { type }
+ } + + return (
+ { authEl } +
) + } + + 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 + } +} diff --git a/src/core/components/auth/auths.jsx b/src/core/components/auth/auths.jsx index f03837d6c53..2110c6cc6da 100644 --- a/src/core/components/auth/auths.jsx +++ b/src/core/components/auth/auths.jsx @@ -27,7 +27,6 @@ export default class Auths extends React.Component { e.preventDefault() let { authActions } = this.props - authActions.authorize(this.state) } @@ -44,8 +43,7 @@ export default class Auths extends React.Component { render() { let { definitions, getComponent, authSelectors, errSelectors } = this.props - const ApiKeyAuth = getComponent("apiKeyAuth") - const BasicAuth = getComponent("basicAuth") + const AuthItem = getComponent("AuthItem") const Oauth2 = getComponent("oauth2", true) const Button = getComponent("Button") @@ -64,33 +62,15 @@ export default class Auths extends React.Component { !!nonOauthDefinitions.size &&
{ nonOauthDefinitions.map( (schema, name) => { - let type = schema.get("type") - let authEl - - switch(type) { - case "apiKey": authEl = - break - case "basic": authEl = - break - default: authEl =
Unknown security definition type { type }
- } - - return (
- { authEl } -
) - + return }).toArray() }
diff --git a/src/core/components/auth/oauth2.jsx b/src/core/components/auth/oauth2.jsx index 0632c2594e9..e6dbe6a5938 100644 --- a/src/core/components/auth/oauth2.jsx +++ b/src/core/components/auth/oauth2.jsx @@ -2,11 +2,6 @@ import React from "react" import PropTypes from "prop-types" 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 { static propTypes = { name: PropTypes.string, @@ -16,6 +11,7 @@ export default class Oauth2 extends React.Component { authSelectors: PropTypes.object.isRequired, authActions: PropTypes.object.isRequired, errSelectors: PropTypes.object.isRequired, + specSelectors: PropTypes.object.isRequired, errActions: PropTypes.object.isRequired, getConfigs: PropTypes.any } @@ -83,7 +79,9 @@ export default class Oauth2 extends React.Component { } render() { - let { schema, getComponent, authSelectors, errSelectors, name } = this.props + let { + schema, getComponent, authSelectors, errSelectors, name, specSelectors + } = this.props const Input = getComponent("Input") const Row = getComponent("Row") const Col = getComponent("Col") @@ -92,6 +90,14 @@ export default class Oauth2 extends React.Component { const JumpToPath = getComponent("JumpToPath", true) 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 scopes = schema.get("allowedScopes") || schema.get("scopes") let authorizedAuth = authSelectors.authorized().get(name) @@ -102,7 +108,7 @@ export default class Oauth2 extends React.Component { return (
-

OAuth2.0

+

{name} (OAuth2, { schema.get("flow") })

{ !this.state.appName ? null :
Application: { this.state.appName }
} { description && } diff --git a/src/core/oauth2-authorize.js b/src/core/oauth2-authorize.js index fd3a4afc646..c5521aaf5d4 100644 --- a/src/core/oauth2-authorize.js +++ b/src/core/oauth2-authorize.js @@ -22,6 +22,16 @@ export default function authorize ( { auth, authActions, errActions, configs, au case "implicit": query.push("response_type=token") break + + case "clientCredentials": + // OAS3 + authActions.authorizeApplication(auth) + return + + case "authorizationCode": + // OAS3 + query.push("response_type=code") + break } if (typeof clientId === "string") { diff --git a/src/core/plugins/auth/reducers.js b/src/core/plugins/auth/reducers.js index 2eca5c08f25..ae28347702a 100644 --- a/src/core/plugins/auth/reducers.js +++ b/src/core/plugins/auth/reducers.js @@ -22,7 +22,7 @@ export default { securities.entrySeq().forEach( ([ key, security ]) => { let type = security.getIn(["schema", "type"]) - if ( type === "apiKey" ) { + if ( type === "apiKey" || type === "http" ) { map = map.set(key, security) } else if ( type === "basic" ) { let username = security.getIn(["value", "username"]) diff --git a/src/core/plugins/oas3/auth-extensions/wrap-selectors.js b/src/core/plugins/oas3/auth-extensions/wrap-selectors.js index 973b208148e..838e2dcd526 100644 --- a/src/core/plugins/oas3/auth-extensions/wrap-selectors.js +++ b/src/core/plugins/oas3/auth-extensions/wrap-selectors.js @@ -1,29 +1,60 @@ import { createSelector } from "reselect" -import { List } from "immutable" +import { List, Map, fromJS } from "immutable" import { isOAS3 as isOAS3Helper } from "../helpers" // Helpers +const state = state => state + function onlyOAS3(selector) { return (ori, system) => (state, ...args) => { const spec = system.getSystem().specSelectors.specJson() if(isOAS3Helper(spec)) { - return selector(...args) + return selector(system, ...args) } else { return ori(...args) } } } -const nullSelector = createSelector(() => null) +export const definitionsToAuthorize = onlyOAS3(createSelector( + state, + ({ specSelectors }) => { + // Coerce our OpenAPI 3.0 definitions into monoflow definitions + // that look like Swagger2 definitions. + let definitions = specSelectors.securityDefinitions() + let list = List() + + definitions.entrySeq().forEach( ([ defName, definition ]) => { + const type = definition.get("type") -const OAS3NullSelector = onlyOAS3(nullSelector) + if(type === "oauth2") { + definition.get("flows").entrySeq().forEach(([flowKey, flowVal]) => { + let translatedDef = fromJS({ + flow: flowKey, + authorizationUrl: flowVal.get("authorizationUrl"), + tokenUrl: flowVal.get("tokenUrl"), + scopes: flowVal.get("scopes"), + type: definition.get("type") + }) -// Hasta la vista, authorization! -export const shownDefinitions = OAS3NullSelector -export const definitionsToAuthorize = OAS3NullSelector -export const getDefinitionsByNames = OAS3NullSelector -export const authorized = onlyOAS3(() => List()) -export const isAuthorized = OAS3NullSelector -export const getConfigs = OAS3NullSelector + 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 + } +)) \ No newline at end of file diff --git a/src/core/plugins/oas3/components/http-auth.jsx b/src/core/plugins/oas3/components/http-auth.jsx new file mode 100644 index 00000000000..bb4727eea7b --- /dev/null +++ b/src/core/plugins/oas3/components/http-auth.jsx @@ -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
+

+ { name || schema.get("name") }  + (http, Basic) + +

+ { username &&
Authorized
} + + + + + + { + username ? { username } + : + } + + + + { + username ? ****** + : + } + + { + errors.valueSeq().map( (error, key) => { + return + } ) + } +
+ } + + if(scheme === "bearer") { + return ( +
+

+ { name || schema.get("name") }  + (http, Bearer) + +

+ { value &&
Authorized
} + + + + +

In: { schema.get("in") }

+
+ + + { + value ? ****** + : + } + + { + errors.valueSeq().map( (error, key) => { + return + } ) + } +
+ ) + } + return
+ {name} HTTP authentication: unsupported or missing scheme +
+ } +} diff --git a/src/core/plugins/oas3/components/index.js b/src/core/plugins/oas3/components/index.js index 642c6689b92..edd409a14ed 100644 --- a/src/core/plugins/oas3/components/index.js +++ b/src/core/plugins/oas3/components/index.js @@ -3,9 +3,11 @@ import RequestBody from "./request-body" import OperationLink from "./operation-link.jsx" import Servers from "./servers" import RequestBodyEditor from "./request-body-editor" +import HttpAuth from "./http-auth" export default { Callbacks, + HttpAuth, RequestBody, Servers, RequestBodyEditor, diff --git a/src/core/plugins/oas3/spec-extensions/wrap-selectors.js b/src/core/plugins/oas3/spec-extensions/wrap-selectors.js index ff892b4f963..78ab05a8496 100644 --- a/src/core/plugins/oas3/spec-extensions/wrap-selectors.js +++ b/src/core/plugins/oas3/spec-extensions/wrap-selectors.js @@ -48,12 +48,16 @@ export const definitions = onlyOAS3(createSelector( spec => spec.getIn(["components", "schemas"]) || Map() )) +export const securityDefinitions = onlyOAS3(createSelector( + spec, + spec => spec.getIn(["components", "securitySchemes"]) || Map() +)) + export const host = OAS3NullSelector export const basePath = OAS3NullSelector export const consumes = OAS3NullSelector export const produces = OAS3NullSelector export const schemes = OAS3NullSelector -export const securityDefinitions = OAS3NullSelector // New selectors diff --git a/src/core/plugins/oas3/wrap-components/auth-item.jsx b/src/core/plugins/oas3/wrap-components/auth-item.jsx new file mode 100644 index 00000000000..bd6df5cb881 --- /dev/null +++ b/src/core/plugins/oas3/wrap-components/auth-item.jsx @@ -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 + } else { + return + } +}) diff --git a/src/core/plugins/oas3/wrap-components/index.js b/src/core/plugins/oas3/wrap-components/index.js index 8b105b38286..910dcf29ca9 100644 --- a/src/core/plugins/oas3/wrap-components/index.js +++ b/src/core/plugins/oas3/wrap-components/index.js @@ -1,4 +1,5 @@ import Markdown from "./markdown" +import AuthItem from "./auth-item" import parameters from "./parameters" import VersionStamp from "./version-stamp" import OnlineValidatorBadge from "./online-validator-badge" @@ -6,6 +7,7 @@ import Model from "./model" export default { Markdown, + AuthItem, parameters, VersionStamp, model: Model, diff --git a/src/core/presets/base.js b/src/core/presets/base.js index 10a12645aaa..ec005b37f42 100644 --- a/src/core/presets/base.js +++ b/src/core/presets/base.js @@ -17,6 +17,7 @@ import AuthorizationPopup from "core/components/auth/authorization-popup" import AuthorizeBtn from "core/components/auth/authorize-btn" import AuthorizeOperationBtn from "core/components/auth/authorize-operation-btn" import Auths from "core/components/auth/auths" +import AuthItem from "core/components/auth/auth-item" import AuthError from "core/components/auth/error" import ApiKeyAuth from "core/components/auth/api-key-auth" import BasicAuth from "core/components/auth/basic-auth" @@ -70,6 +71,7 @@ export default function() { authorizeBtn: AuthorizeBtn, authorizeOperationBtn: AuthorizeOperationBtn, auths: Auths, + AuthItem: AuthItem, authError: AuthError, oauth2: Oauth2, apiKeyAuth: ApiKeyAuth, diff --git a/test/core/plugins/oas3/wrap-auth-selectors.js b/test/core/plugins/oas3/wrap-auth-selectors.js new file mode 100644 index 00000000000..1687d767b32 --- /dev/null +++ b/test/core/plugins/oas3/wrap-auth-selectors.js @@ -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.only("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" + } + }, + ]) + + }) + + }) + +})