diff --git a/src/core/components/auth/scope-display.jsx b/src/core/components/auth/scope-display.jsx new file mode 100644 index 00000000000..b7f169b0583 --- /dev/null +++ b/src/core/components/auth/scope-display.jsx @@ -0,0 +1,130 @@ +import React from "react" +import PropTypes from "prop-types" +import ImPropTypes from "react-immutable-proptypes" + +export default class ScopeDisplay extends React.Component { + static propTypes = { + security: ImPropTypes.iterable, + authSelectors: PropTypes.object.isRequired, + authDefinitions: ImPropTypes.iterable, + specSelectors: PropTypes.object.isRequired + } + + extractSecurityRequirements = (security) => { + if (!security || !security.count()) { + return null + } + + const requirements = [] + + // Each item in security array represents an OR condition + security.forEach((requirement) => { + const schemes = [] + + // Each entry in a requirement represents an AND condition + requirement.forEach((scopes, schemeName) => { + const schemeData = { + name: schemeName, + scopes: [] + } + + // Handle different security scheme types + if (scopes && scopes.size > 0) { + // For OAuth2, OpenID Connect, or any scheme with scopes + schemeData.scopes = scopes.toJS() + } + + schemes.push(schemeData) + }) + + requirements.push(schemes) + }) + + return requirements + } + + formatNonOptionalRequirements = (requirements) => { + return requirements.map((requirementGroup, idx) => { + const isLastGroup = idx === requirements.length - 1 + + return ( + + {requirementGroup.map((scheme, schemeIdx) => { + const isLastInGroup = schemeIdx === requirementGroup.length - 1 + + return ( + + {scheme.name} + {scheme.scopes.length > 0 && ( + + {" ("} + {scheme.scopes.map((scope, scopeIdx) => ( + + + {scope} + + {scopeIdx < scheme.scopes.length - 1 ? ", " : ""} + + ))} + {")"} + + )} + {!isLastInGroup && ( + + + )} + + ) + })} + {!isLastGroup && ( + OR + )} + + ) + }) + } + + formatSecurityDisplay = (requirements) => { + if (!requirements || requirements.length === 0) { + return null + } + + // Check if this is optional security (empty object in array) + if (requirements.length === 1 && requirements[0].length === 0) { + return Optional + } + + // Check for optional security pattern (one empty and others with auth) + const hasEmptyRequirement = requirements.some(req => req.length === 0) + const hasNonEmptyRequirement = requirements.some(req => req.length > 0) + + if (hasEmptyRequirement && hasNonEmptyRequirement) { + // Filter out empty requirements and add optional label + const nonEmptyRequirements = requirements.filter(req => req.length > 0) + return ( + + Optional + OR + {this.formatNonOptionalRequirements(nonEmptyRequirements)} + + ) + } + + return this.formatNonOptionalRequirements(requirements) + } + + render() { + const { security } = this.props + const requirements = this.extractSecurityRequirements(security) + const display = this.formatSecurityDisplay(requirements) + + if (!display) { + return null + } + + return ( +
+ {display} +
+ ) + } +} \ No newline at end of file diff --git a/src/core/components/operation-summary.jsx b/src/core/components/operation-summary.jsx index d31f56d3f9c..90881f50f4c 100644 --- a/src/core/components/operation-summary.jsx +++ b/src/core/components/operation-summary.jsx @@ -16,6 +16,7 @@ export default class OperationSummary extends PureComponent { getConfigs: PropTypes.func.isRequired, authActions: PropTypes.object, authSelectors: PropTypes.object, + specSelectors: PropTypes.object, } static defaultProps = { @@ -32,6 +33,7 @@ export default class OperationSummary extends PureComponent { getComponent, authActions, authSelectors, + specSelectors, operationProps, specPath, } = this.props @@ -55,6 +57,7 @@ export default class OperationSummary extends PureComponent { let security = operationProps.get("security") const AuthorizeOperationBtn = getComponent("authorizeOperationBtn", true) + const ScopeDisplay = getComponent("ScopeDisplay", true) const OperationSummaryMethod = getComponent("OperationSummaryMethod") const OperationSummaryPath = getComponent("OperationSummaryPath") const JumpToPath = getComponent("JumpToPath", true) @@ -88,13 +91,21 @@ export default class OperationSummary extends PureComponent { { allowAnonymous ? null : - { - const applicableDefinitions = authSelectors.definitionsForRequirements(security) - authActions.showDefinitions(applicableDefinitions) - }} - /> +
+ + { + const applicableDefinitions = authSelectors.definitionsForRequirements(security) + authActions.showDefinitions(applicableDefinitions) + }} + /> +
} {/* TODO: use wrapComponents here, swagger-ui doesn't care about jumpToPath */}