diff --git a/src/core/plugins/oas3/components/http-auth.jsx b/src/core/plugins/oas3/components/auth/http-auth.jsx similarity index 100% rename from src/core/plugins/oas3/components/http-auth.jsx rename to src/core/plugins/oas3/components/auth/http-auth.jsx diff --git a/src/core/plugins/oas3/components/index.js b/src/core/plugins/oas3/components/index.js index ff180b7e..7ab125d1 100644 --- a/src/core/plugins/oas3/components/index.js +++ b/src/core/plugins/oas3/components/index.js @@ -4,7 +4,7 @@ import OperationLink from "./operation-link" import Servers from "./servers" import ServersContainer from "./servers-container" import RequestBodyEditor from "./request-body-editor" -import HttpAuth from "./http-auth" +import HttpAuth from "./auth/http-auth" import OperationServers from "./operation-servers" export default { diff --git a/src/core/plugins/oas3/index.js b/src/core/plugins/oas3/index.js index c96fac6f..7d63ce0e 100644 --- a/src/core/plugins/oas3/index.js +++ b/src/core/plugins/oas3/index.js @@ -23,9 +23,9 @@ export default function () { wrapSelectors: authWrapSelectors, }, oas3: { - actions, + actions: { ...actions }, reducers, - selectors, + selectors: { ...selectors }, }, }, } diff --git a/src/core/plugins/oas3/wrap-components/auth-item.jsx b/src/core/plugins/oas3/wrap-components/auth/auth-item.jsx similarity index 91% rename from src/core/plugins/oas3/wrap-components/auth-item.jsx rename to src/core/plugins/oas3/wrap-components/auth/auth-item.jsx index bd6df5cb..87a2cde3 100644 --- a/src/core/plugins/oas3/wrap-components/auth-item.jsx +++ b/src/core/plugins/oas3/wrap-components/auth/auth-item.jsx @@ -1,5 +1,5 @@ import React from "react" -import { OAS3ComponentWrapFactory } from "../helpers" +import { OAS3ComponentWrapFactory } from "../../helpers" export default OAS3ComponentWrapFactory(({ Ori, ...props }) => { const { @@ -9,6 +9,7 @@ export default OAS3ComponentWrapFactory(({ Ori, ...props }) => { const HttpAuth = getComponent("HttpAuth") const type = schema.get("type") + if(type === "http") { return (oriSelector, system) => { + const definitions = system.specSelectors.securityDefinitions() + let list = oriSelector() + + if (!definitions) return list + + definitions.entrySeq().forEach(([defName, definition]) => { + const type = definition.get("type") + + if (type === "mutualTLS") { + list = list.push( + new Map({ + [defName]: definition, + }) + ) + } + }) + + return list + } +) diff --git a/src/core/plugins/oas31/components/auth/auths.jsx b/src/core/plugins/oas31/components/auth/auths.jsx new file mode 100644 index 00000000..73839cce --- /dev/null +++ b/src/core/plugins/oas31/components/auth/auths.jsx @@ -0,0 +1,184 @@ +/** + * @prettier + */ +import React from "react" +import PropTypes from "prop-types" +import ImPropTypes from "react-immutable-proptypes" + +class Auths extends React.Component { + static propTypes = { + definitions: ImPropTypes.iterable.isRequired, + getComponent: PropTypes.func.isRequired, + authSelectors: PropTypes.object.isRequired, + authActions: PropTypes.object.isRequired, + errSelectors: PropTypes.object.isRequired, + specSelectors: PropTypes.object.isRequired, + } + + constructor(props, context) { + super(props, context) + + this.state = {} + } + + onAuthChange = (auth) => { + let { name } = auth + + this.setState({ [name]: auth }) + } + + submitAuth = (e) => { + e.preventDefault() + + let { authActions } = this.props + authActions.authorizeWithPersistOption(this.state) + } + + logoutClick = (e) => { + e.preventDefault() + + let { authActions, definitions } = this.props + let auths = definitions + .map((val, key) => { + return key + }) + .toArray() + + this.setState( + auths.reduce((prev, auth) => { + prev[auth] = "" + return prev + }, {}) + ) + + authActions.logoutWithPersistOption(auths) + } + + close = (e) => { + e.preventDefault() + let { authActions } = this.props + + authActions.showDefinitions(false) + } + + render() { + let { definitions, getComponent, authSelectors, errSelectors } = this.props + const AuthItem = getComponent("AuthItem") + const Oauth2 = getComponent("oauth2", true) + const Button = getComponent("Button") + + const authorized = authSelectors.authorized() + const authorizedAuth = definitions.filter((definition, key) => { + return !!authorized.get(key) + }) + const nonOauthDefinitions = definitions.filter( + (schema) => + schema.get("type") !== "oauth2" && schema.get("type") !== "mutualTLS" + ) + const oauthDefinitions = definitions.filter( + (schema) => schema.get("type") === "oauth2" + ) + const mutualTLSDefinitions = definitions.filter( + (schema) => schema.get("type") === "mutualTLS" + ) + return ( +
+ {nonOauthDefinitions.size > 0 && ( +
+ {nonOauthDefinitions + .map((schema, name) => { + return ( + + ) + }) + .toArray()} +
+ {nonOauthDefinitions.size === authorizedAuth.size ? ( + + ) : ( + + )} + +
+ + )} + + {oauthDefinitions.size > 0 ? ( +
+
+

+ Scopes are used to grant an application different levels of + access to data on behalf of the end user. Each API may declare + one or more scopes. +

+

+ API requires the following scopes. Select which ones you want to + grant to Swagger UI. +

+
+ {definitions + .filter((schema) => schema.get("type") === "oauth2") + .map((schema, name) => { + return ( +
+ +
+ ) + }) + .toArray()} +
+ ) : null} + {mutualTLSDefinitions.size > 0 && ( +
+ {mutualTLSDefinitions + .map((schema, name) => { + return ( + + ) + }) + .toArray()} +
+ )} +
+ ) + } +} + +export default Auths diff --git a/src/core/plugins/oas31/components/auth/mutual-tls-auth.jsx b/src/core/plugins/oas31/components/auth/mutual-tls-auth.jsx new file mode 100644 index 00000000..a9ca8ba2 --- /dev/null +++ b/src/core/plugins/oas31/components/auth/mutual-tls-auth.jsx @@ -0,0 +1,29 @@ +/** + * @prettier + */ +import React from "react" +import PropTypes from "prop-types" + +const MutualTLSAuth = ({ schema, getComponent }) => { + const JumpToPath = getComponent("JumpToPath", true) + return ( +
+

+ {schema.get("name")} (mutualTLS){" "} + +

+

+ Mutual TLS is required by this API/Operation. Certificates are managed + via your Operating System and/or your browser. +

+

{schema.get("description")}

+
+ ) +} + +MutualTLSAuth.propTypes = { + schema: PropTypes.object.isRequired, + getComponent: PropTypes.func.isRequired, +} + +export default MutualTLSAuth diff --git a/src/core/plugins/oas31/index.js b/src/core/plugins/oas31/index.js index 3527a20f..11900727 100644 --- a/src/core/plugins/oas31/index.js +++ b/src/core/plugins/oas31/index.js @@ -9,12 +9,16 @@ import JsonSchemaDialect from "./components/json-schema-dialect" import VersionPragmaFilter from "./components/version-pragma-filter" import Model from "./components/model/model" import Models from "./components/models/models" +import MutualTLSAuth from "./components/auth/mutual-tls-auth" +import Auths from "./components/auth/auths" import LicenseWrapper from "./wrap-components/license" import ContactWrapper from "./wrap-components/contact" import InfoWrapper from "./wrap-components/info" import ModelWrapper from "./wrap-components/model" import ModelsWrapper from "./wrap-components/models" import VersionPragmaFilterWrapper from "./wrap-components/version-pragma-filter" +import AuthItemWrapper from "./wrap-components/auth/auth-item" +import AuthsWrapper from "./wrap-components/auths" import { isOAS31 as isOAS31Fn, createOnlyOAS31Selector as createOnlyOAS31SelectorFn, @@ -50,6 +54,7 @@ import { isOAS3 as isOAS3SelectorWrapper, selectLicenseUrl as selectLicenseUrlWrapper, } from "./spec-extensions/wrap-selectors" +import { definitionsToAuthorize as definitionsToAuthorizeWrapper } from "./auth-extensions/wrap-selectors" import { selectLicenseUrl as selectOAS31LicenseUrl } from "./selectors" import JSONSchema202012KeywordExample from "./json-schema-2020-12-extensions/components/keywords/Example" import JSONSchema202012KeywordXml from "./json-schema-2020-12-extensions/components/keywords/Xml" @@ -74,12 +79,14 @@ const OAS31Plugin = ({ fn }) => { components: { Webhooks, JsonSchemaDialect, + MutualTLSAuth, OAS31Info: Info, OAS31License: License, OAS31Contact: Contact, OAS31VersionPragmaFilter: VersionPragmaFilter, OAS31Model: Model, OAS31Models: Models, + OAS31Auths: Auths, JSONSchema202012KeywordExample, JSONSchema202012KeywordXml, JSONSchema202012KeywordDiscriminator, @@ -92,6 +99,8 @@ const OAS31Plugin = ({ fn }) => { VersionPragmaFilter: VersionPragmaFilterWrapper, Model: ModelWrapper, Models: ModelsWrapper, + AuthItem: AuthItemWrapper, + auths: AuthsWrapper, JSONSchema202012KeywordDescription: JSONSchema202012KeywordDescriptionWrapper, JSONSchema202012KeywordDefault: JSONSchema202012KeywordDefaultWrapper, @@ -99,6 +108,11 @@ const OAS31Plugin = ({ fn }) => { JSONSchema202012KeywordPropertiesWrapper, }, statePlugins: { + auth: { + wrapSelectors: { + definitionsToAuthorize: definitionsToAuthorizeWrapper, + }, + }, spec: { selectors: { isOAS31: createSystemSelector(selectIsOAS31), diff --git a/src/core/plugins/oas31/wrap-components/auth/auth-item.jsx b/src/core/plugins/oas31/wrap-components/auth/auth-item.jsx new file mode 100644 index 00000000..2471d862 --- /dev/null +++ b/src/core/plugins/oas31/wrap-components/auth/auth-item.jsx @@ -0,0 +1,22 @@ +/** + * @prettier + */ +import React from "react" + +import { createOnlyOAS31ComponentWrapper } from "../../fn" + +const AuthItem = createOnlyOAS31ComponentWrapper( + ({ originalComponent: Ori, ...props }) => { + const { getComponent, schema } = props + const MutualTLSAuth = getComponent("MutualTLSAuth", true) + const type = schema.get("type") + + if (type === "mutualTLS") { + return + } + + return + } +) + +export default AuthItem diff --git a/src/core/plugins/oas31/wrap-components/auths.jsx b/src/core/plugins/oas31/wrap-components/auths.jsx new file mode 100644 index 00000000..308cb2ff --- /dev/null +++ b/src/core/plugins/oas31/wrap-components/auths.jsx @@ -0,0 +1,17 @@ +/** + * @prettier + */ +import React from "react" + +import { createOnlyOAS31ComponentWrapper } from "../fn" + +const AuthsWrapper = createOnlyOAS31ComponentWrapper( + ({ getSystem, ...props }) => { + const system = getSystem() + const OAS31Auths = system.getComponent("OAS31Auths", true) + + return + } +) + +export default AuthsWrapper diff --git a/test/e2e-cypress/e2e/features/oas31-auth-mutual-tls.cy.js b/test/e2e-cypress/e2e/features/oas31-auth-mutual-tls.cy.js new file mode 100644 index 00000000..6bd65730 --- /dev/null +++ b/test/e2e-cypress/e2e/features/oas31-auth-mutual-tls.cy.js @@ -0,0 +1,30 @@ +describe("MutualTLS Authorization", () => { + + it("should open authorization popup", () => { + cy.visit( + "/?url=/documents/security/mutual-tls.yaml" + ) + .get("button.authorize") + .click() + .get(".auth-container h4") + .contains("mutual (mutualTLS)") + }) + it("should have description given by user", () => { + cy.visit( + "/?url=/documents/security/mutual-tls.yaml" + ) + .get("button.authorize") + .click() + .get(".auth-container p:nth-of-type(2)") + .contains("Mutual TLS description") + }) + it("should not display Authorize or Logout buttons", () => { + cy.visit( + "/?url=/documents/security/mutual-tls.yaml" + ) + .get("button.authorize") + .click() + .get(".auth-button-wrapper") + .should("not.exist") + }) +}) diff --git a/test/e2e-cypress/static/documents/security/mutual-tls.yaml b/test/e2e-cypress/static/documents/security/mutual-tls.yaml new file mode 100644 index 00000000..502b9b9a --- /dev/null +++ b/test/e2e-cypress/static/documents/security/mutual-tls.yaml @@ -0,0 +1,10 @@ +openapi: 3.1.0 +info: + title: Test Mutual TLS authorization + version: 1.0.0 +components: + securitySchemes: + mutual: + type: mutualTLS + description: Mutual TLS description + name: mutual