feat: initial display security requirements

This commit is contained in:
Roman Hotsiy 2017-12-15 12:17:14 +02:00
parent 9be40718f4
commit 50e2a5868d
No known key found for this signature in database
GPG Key ID: 5CB7B3ACABA57CB0
11 changed files with 131 additions and 13 deletions

View File

@ -73,7 +73,7 @@ export const PropertyNameCell = PropertyCell.extend`
export const PropertyDetailsCell = styled.td` export const PropertyDetailsCell = styled.td`
border-bottom: 1px solid #9fb4be; border-bottom: 1px solid #9fb4be;
padding: 10px 0; padding: 10px 0;
width: 75%; width: ${props => props.theme.schemaView.defaultDetailsWidth};
box-sizing: border-box; box-sizing: border-box;
tr.expanded & { tr.expanded & {

View File

@ -1,9 +1,10 @@
import * as React from 'react'; import * as React from 'react';
import styled from '../../styled-components'; import styled from '../../styled-components';
import { SecurityRequirements } from '../SecurityRequirement/SecuirityRequirement';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import { H2, MiddlePanel, DarkRightPanel, Badge, Row } from '../../common-elements'; import { Badge, DarkRightPanel, H2, MiddlePanel, Row } from '../../common-elements';
import { ComponentWithOptions } from '../OptionsProvider'; import { ComponentWithOptions } from '../OptionsProvider';
@ -53,6 +54,7 @@ export class Operation extends ComponentWithOptions<OperationProps> {
</H2> </H2>
{pathInMiddle && <Endpoint operation={operation} inverted={true} />} {pathInMiddle && <Endpoint operation={operation} inverted={true} />}
{description !== undefined && <Markdown source={description} />} {description !== undefined && <Markdown source={description} />}
<SecurityRequirements securities={operation.security} />
<Parameters parameters={operation.parameters} body={operation.requestBody} /> <Parameters parameters={operation.parameters} body={operation.requestBody} />
<ResponsesList responses={operation.responses} /> <ResponsesList responses={operation.responses} />
</MiddlePanel> </MiddlePanel>

View File

@ -0,0 +1,72 @@
import * as React from 'react';
import styled from '../../styled-components';
import { transparentizeHex } from '../../utils/styled';
import { SecurityRequirementModel } from '../../services/models/SecurityRequirement';
import { UnderlinedHeader } from '../../common-elements/headers';
const ScopeName = styled.code`
font-size: ${props => props.theme.code.fontSize};
font-family: ${props => props.theme.code.fontFamily};
border: 1px solid ${props => transparentizeHex(props.theme.colors.text, 0.15)};
margin: 0 3px;
padding: 0.2em;
display: inline-block;
line-height: 1;
`;
export interface SecurityRequirementProps {
security: SecurityRequirementModel;
}
export class SecurityRequirement extends React.PureComponent<SecurityRequirementProps> {
render() {
const security = this.props.security;
return security.schemes.map((scheme, idx) => {
return (
<div key={scheme.id}>
<a href={'#' + scheme.sectionId}>{scheme.id}</a>
{scheme.scopes.length > 0 && ' ('}
{scheme.scopes.map(scope => <ScopeName key={scope}>{scope}</ScopeName>)}
{scheme.scopes.length > 0 && ') '}
{idx < security.schemes.length - 1 && ' and '}
</div>
);
});
}
}
const AuthHeaderColumn = styled.div`
display: inline-block;
width: calc(100% - ${props => props.theme.schemaView.defaultDetailsWidth});
`;
const SecuritiesColumn = styled.div`
width: ${props => props.theme.schemaView.defaultDetailsWidth};
display: inline-block;
`;
const AuthHeader = styled(UnderlinedHeader)`
display: inline-block;
`;
export interface SecurityRequirementsProps {
securities: SecurityRequirementModel[];
}
export class SecurityRequirements extends React.PureComponent<SecurityRequirementsProps> {
render() {
const securities = this.props.securities;
if (!securities.length) return null;
return (
<div>
<AuthHeaderColumn>
<AuthHeader>Authorizations: </AuthHeader>
</AuthHeaderColumn>
<SecuritiesColumn>
{securities.map((security, idx) => <SecurityRequirement key={idx} security={security} />)}
</SecuritiesColumn>
</div>
);
}
}

View File

@ -3,7 +3,7 @@ import * as React from 'react';
import { SecuritySchemesModel } from '../../services/models'; import { SecuritySchemesModel } from '../../services/models';
import styled from '../../styled-components'; import styled from '../../styled-components';
import { H2 } from '../../common-elements'; import { H2, ShareLink } from '../../common-elements';
import { Markdown } from '../Markdown/Markdown'; import { Markdown } from '../Markdown/Markdown';
import { OpenAPISecurityScheme } from '../../types'; import { OpenAPISecurityScheme } from '../../types';
@ -81,8 +81,11 @@ export class SecurityDefs extends React.PureComponent<SecurityDefsProps> {
return ( return (
<div> <div>
{this.props.securitySchemes.schemes.map(scheme => ( {this.props.securitySchemes.schemes.map(scheme => (
<div key={scheme.id}> <div data-section-id={scheme.sectionId} key={scheme.id}>
<H2>{scheme.id}</H2> <H2>
<ShareLink href={'#' + scheme.sectionId} />
{scheme.id}
</H2>
<Markdown source={scheme.description || ''} /> <Markdown source={scheme.description || ''} />
<AuthTable className="security-details"> <AuthTable className="security-details">
<tbody> <tbody>

View File

@ -7,7 +7,7 @@ import { JsonPointer } from '../utils/JsonPointer';
import { isNamedDefinition } from '../utils/openapi'; import { isNamedDefinition } from '../utils/openapi';
import { COMPONENT_REGEXP, buildComponentComment } from './MarkdownRenderer'; import { COMPONENT_REGEXP, buildComponentComment } from './MarkdownRenderer';
import { RedocNormalizedOptions } from './RedocNormalizedOptions'; import { RedocNormalizedOptions } from './RedocNormalizedOptions';
import { appendToMdHeading } from '../utils/index'; import { appendToMdHeading } from '../utils/';
export type MergedOpenAPISchema = OpenAPISchema & { parentRefs?: string[] }; export type MergedOpenAPISchema = OpenAPISchema & { parentRefs?: string[] };

View File

@ -3,6 +3,7 @@ import { join as joinPaths } from 'path';
import { parse as urlParse } from 'url'; import { parse as urlParse } from 'url';
import { IMenuItem } from '../MenuStore'; import { IMenuItem } from '../MenuStore';
import { SecurityRequirementModel } from './SecurityRequirement';
import { GroupModel } from './Group.model'; import { GroupModel } from './Group.model';
import { OpenAPIExternalDocumentation, OpenAPIServer } from '../../types'; import { OpenAPIExternalDocumentation, OpenAPIServer } from '../../types';
@ -16,10 +17,6 @@ import { ContentItemModel, ExtendedOpenAPIOperation } from '../MenuBuilder';
import { JsonPointer, getOperationSummary, isAbsolutePath, stripTrailingSlash } from '../../utils'; import { JsonPointer, getOperationSummary, isAbsolutePath, stripTrailingSlash } from '../../utils';
import { RedocNormalizedOptions } from '../RedocNormalizedOptions'; import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
function isNumeric(n) {
return !isNaN(parseFloat(n)) && isFinite(n);
}
/** /**
* Operation model ready to be used by components * Operation model ready to be used by components
*/ */
@ -50,6 +47,7 @@ export class OperationModel implements IMenuItem {
responses: ResponseModel[]; responses: ResponseModel[];
path: string; path: string;
servers: OpenAPIServer[]; servers: OpenAPIServer[];
security: SecurityRequirementModel[];
codeSamples: CodeSample[]; codeSamples: CodeSample[];
constructor( constructor(
@ -101,6 +99,10 @@ export class OperationModel implements IMenuItem {
parser.specUrl, parser.specUrl,
operationSpec.servers || parser.spec.servers || [], operationSpec.servers || parser.spec.servers || [],
); );
this.security = (operationSpec.security || parser.spec.security || []).map(
security => new SecurityRequirementModel(security, parser),
);
} }
/** /**
@ -126,6 +128,10 @@ export class OperationModel implements IMenuItem {
} }
} }
function isNumeric(n) {
return !isNaN(parseFloat(n)) && isFinite(n);
}
function normalizeServers(specUrl: string, servers: OpenAPIServer[]): OpenAPIServer[] { function normalizeServers(specUrl: string, servers: OpenAPIServer[]): OpenAPIServer[] {
if (servers.length === 0) { if (servers.length === 0) {
return [ return [

View File

@ -0,0 +1,27 @@
import { OpenAPISecurityRequirement } from '../../types';
import { OpenAPIParser } from '../OpenAPIParser';
import { SECURITY_SCHEMES_SECTION } from '../../utils/openapi';
export class SecurityRequirementModel {
schemes: {
id: string;
sectionId: string;
type: string;
scopes: string[];
}[];
constructor(requirement: OpenAPISecurityRequirement, parser: OpenAPIParser) {
const schemes = (parser.spec.components && parser.spec.components.securitySchemes) || {};
this.schemes = Object.keys(requirement || {}).map(id => {
const scheme = parser.deref(schemes[id]);
const scopes = requirement[id] || [];
return {
id,
sectionId: SECURITY_SCHEMES_SECTION + id,
type: scheme.type,
scopes,
};
});
}
}

View File

@ -1,8 +1,10 @@
import { OpenAPISecurityScheme, Referenced } from '../../types'; import { OpenAPISecurityScheme, Referenced } from '../../types';
import { OpenAPIParser } from '../OpenAPIParser'; import { OpenAPIParser } from '../OpenAPIParser';
import { SECURITY_SCHEMES_SECTION } from '../../utils/openapi';
export class SecuritySchemeModel { export class SecuritySchemeModel {
id: string; id: string;
sectionId: string;
type: OpenAPISecurityScheme['type']; type: OpenAPISecurityScheme['type'];
description: string; description: string;
apiKey?: { apiKey?: {
@ -23,6 +25,7 @@ export class SecuritySchemeModel {
constructor(parser: OpenAPIParser, id: string, scheme: Referenced<OpenAPISecurityScheme>) { constructor(parser: OpenAPIParser, id: string, scheme: Referenced<OpenAPISecurityScheme>) {
const info = parser.deref(scheme); const info = parser.deref(scheme);
this.id = id; this.id = id;
this.sectionId = SECURITY_SCHEMES_SECTION + id;
this.type = info.type; this.type = info.type;
this.description = info.description || ''; this.description = info.description || '';
if (info.type === 'apiKey') { if (info.type === 'apiKey') {
@ -54,7 +57,7 @@ export class SecuritySchemeModel {
export class SecuritySchemesModel { export class SecuritySchemesModel {
schemes: SecuritySchemeModel[]; schemes: SecuritySchemeModel[];
constructor(public parser: OpenAPIParser) { constructor(parser: OpenAPIParser) {
const schemes = (parser.spec.components && parser.spec.components.securitySchemes) || {}; const schemes = (parser.spec.components && parser.spec.components.securitySchemes) || {};
this.schemes = Object.keys(schemes).map( this.schemes = Object.keys(schemes).map(
name => new SecuritySchemeModel(parser, name, schemes[name]), name => new SecuritySchemeModel(parser, name, schemes[name]),

View File

@ -21,6 +21,7 @@ const theme = {
}, },
schemaView: { schemaView: {
linesColor: '#7f99cf', linesColor: '#7f99cf',
defaultDetailsWidth: '75%',
}, },
baseFont: { baseFont: {
size: '14px', size: '14px',
@ -35,7 +36,7 @@ const theme = {
}, },
code: { code: {
fontSize: '13px', fontSize: '13px',
fontFamily: '"Lucida Console", Monaco, monospace', fontFamily: 'Courirer, monospace',
}, },
links: { links: {
color: undefined, // by default main color color: undefined, // by default main color

View File

@ -200,7 +200,9 @@ export type OpenAPIComponents = {
callbacks?: { [name: string]: Referenced<OpenAPICallback> }; callbacks?: { [name: string]: Referenced<OpenAPICallback> };
}; };
export type OpenAPISecurityRequirement = {}; export type OpenAPISecurityRequirement = {
[name: string]: string[];
};
export type OpenAPISecurityScheme = { export type OpenAPISecurityScheme = {
type: 'apiKey' | 'http' | 'oauth2' | 'openIdConnect'; type: 'apiKey' | 'http' | 'oauth2' | 'openIdConnect';

View File

@ -161,3 +161,5 @@ export function humanizeConstraints(schema: OpenAPISchema): string[] {
return res; return res;
} }
export const SECURITY_SCHEMES_SECTION = 'section/Authentication/';