Skip to content

Commit

Permalink
feat: add mutualTLS auth option (#9193)
Browse files Browse the repository at this point in the history
Refs #8020 

Co-authored-by: Vladimír Gorej <vladimir.gorej@smartbear.com>
  • Loading branch information
tekyu and char0n committed Sep 13, 2023
1 parent 8db1226 commit 89cdd7b
Show file tree
Hide file tree
Showing 13 changed files with 340 additions and 5 deletions.
2 changes: 1 addition & 1 deletion src/core/plugins/oas3/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions src/core/plugins/oas3/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ export default function () {
wrapSelectors: authWrapSelectors,
},
oas3: {
actions,
actions: { ...actions },
reducers,
selectors,
selectors: { ...selectors },
},
},
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from "react"
import { OAS3ComponentWrapFactory } from "../helpers"
import { OAS3ComponentWrapFactory } from "../../helpers"

export default OAS3ComponentWrapFactory(({ Ori, ...props }) => {
const {
Expand All @@ -9,6 +9,7 @@ export default OAS3ComponentWrapFactory(({ Ori, ...props }) => {
const HttpAuth = getComponent("HttpAuth")
const type = schema.get("type")


if(type === "http") {
return <HttpAuth key={ name }
schema={ schema }
Expand Down
2 changes: 1 addition & 1 deletion src/core/plugins/oas3/wrap-components/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Markdown from "./markdown"
import AuthItem from "./auth-item"
import AuthItem from "./auth/auth-item"
import OnlineValidatorBadge from "./online-validator-badge"
import Model from "./model"
import JsonSchema_string from "./json-schema-string"
Expand Down
28 changes: 28 additions & 0 deletions src/core/plugins/oas31/auth-extensions/wrap-selectors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* @prettier
*/
import { Map } from "immutable"
import { createOnlyOAS31SelectorWrapper } from "../fn"

export const definitionsToAuthorize = createOnlyOAS31SelectorWrapper(
() => (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
}
)
184 changes: 184 additions & 0 deletions src/core/plugins/oas31/components/auth/auths.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="auth-container">
{nonOauthDefinitions.size > 0 && (
<form onSubmit={this.submitAuth}>
{nonOauthDefinitions
.map((schema, name) => {
return (
<AuthItem
key={name}
schema={schema}
name={name}
getComponent={getComponent}
onAuthChange={this.onAuthChange}
authorized={authorized}
errSelectors={errSelectors}
/>
)
})
.toArray()}
<div className="auth-btn-wrapper">
{nonOauthDefinitions.size === authorizedAuth.size ? (
<Button
className="btn modal-btn auth"
onClick={this.logoutClick}
aria-label="Remove authorization"
>
Logout
</Button>
) : (
<Button
type="submit"
className="btn modal-btn auth authorize"
aria-label="Apply credentials"
>
Authorize
</Button>
)}
<Button
className="btn modal-btn auth btn-done"
onClick={this.close}
>
Close
</Button>
</div>
</form>
)}

{oauthDefinitions.size > 0 ? (
<div>
<div className="scope-def">
<p>
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.
</p>
<p>
API requires the following scopes. Select which ones you want to
grant to Swagger UI.
</p>
</div>
{definitions
.filter((schema) => schema.get("type") === "oauth2")
.map((schema, name) => {
return (
<div key={name}>
<Oauth2
authorized={authorized}
schema={schema}
name={name}
/>
</div>
)
})
.toArray()}
</div>
) : null}
{mutualTLSDefinitions.size > 0 && (
<div>
{mutualTLSDefinitions
.map((schema, name) => {
return (
<AuthItem
key={name}
schema={schema}
name={name}
getComponent={getComponent}
onAuthChange={this.onAuthChange}
authorized={authorized}
errSelectors={errSelectors}
/>
)
})
.toArray()}
</div>
)}
</div>
)
}
}

export default Auths
29 changes: 29 additions & 0 deletions src/core/plugins/oas31/components/auth/mutual-tls-auth.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* @prettier
*/
import React from "react"
import PropTypes from "prop-types"

const MutualTLSAuth = ({ schema, getComponent }) => {
const JumpToPath = getComponent("JumpToPath", true)
return (
<div>
<h4>
{schema.get("name")} (mutualTLS){" "}
<JumpToPath path={["securityDefinitions", schema.get("name")]} />
</h4>
<p>
Mutual TLS is required by this API/Operation. Certificates are managed
via your Operating System and/or your browser.
</p>
<p>{schema.get("description")}</p>
</div>
)
}

MutualTLSAuth.propTypes = {
schema: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired,
}

export default MutualTLSAuth
14 changes: 14 additions & 0 deletions src/core/plugins/oas31/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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"
Expand All @@ -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,
Expand All @@ -92,13 +99,20 @@ const OAS31Plugin = ({ fn }) => {
VersionPragmaFilter: VersionPragmaFilterWrapper,
Model: ModelWrapper,
Models: ModelsWrapper,
AuthItem: AuthItemWrapper,
auths: AuthsWrapper,
JSONSchema202012KeywordDescription:
JSONSchema202012KeywordDescriptionWrapper,
JSONSchema202012KeywordDefault: JSONSchema202012KeywordDefaultWrapper,
JSONSchema202012KeywordProperties:
JSONSchema202012KeywordPropertiesWrapper,
},
statePlugins: {
auth: {
wrapSelectors: {
definitionsToAuthorize: definitionsToAuthorizeWrapper,
},
},
spec: {
selectors: {
isOAS31: createSystemSelector(selectIsOAS31),
Expand Down
22 changes: 22 additions & 0 deletions src/core/plugins/oas31/wrap-components/auth/auth-item.jsx
Original file line number Diff line number Diff line change
@@ -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 <MutualTLSAuth schema={schema} />
}

return <Ori {...props} />
}
)

export default AuthItem
17 changes: 17 additions & 0 deletions src/core/plugins/oas31/wrap-components/auths.jsx
Original file line number Diff line number Diff line change
@@ -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 <OAS31Auths {...props} />
}
)

export default AuthsWrapper

0 comments on commit 89cdd7b

Please sign in to comment.