diff --git a/src/models/oauth-flow.ts b/src/models/oauth-flow.ts new file mode 100644 index 000000000..0fdbc90e6 --- /dev/null +++ b/src/models/oauth-flow.ts @@ -0,0 +1,10 @@ +import { BaseModel } from './base'; +import { ExtensionsMixinInterface } from './mixins'; + +export interface OAuthFlowInterface extends BaseModel, ExtensionsMixinInterface { + authorizationUrl(): string | undefined; + hasRefreshUrl(): boolean; + refreshUrl(): string | undefined; + scopes(): Record | undefined; + tokenUrl(): string | undefined; +} \ No newline at end of file diff --git a/src/models/oauth-flows.ts b/src/models/oauth-flows.ts new file mode 100644 index 000000000..b9e040d3c --- /dev/null +++ b/src/models/oauth-flows.ts @@ -0,0 +1,14 @@ +import { OAuthFlowInterface } from './oauth-flow'; +import { BaseModel } from './base'; +import {ExtensionsMixinInterface} from './mixins'; + +export interface OAuthFlowsInterface extends BaseModel, ExtensionsMixinInterface { + hasAuthorizationCode(): boolean; + authorizationCode(): OAuthFlowInterface | undefined; + hasClientCredentials(): boolean + clientCredentials(): OAuthFlowInterface | undefined; + hasImplicit(): boolean; + implicit(): OAuthFlowInterface | undefined; + hasPassword(): boolean; + password(): OAuthFlowInterface | undefined; +} \ No newline at end of file diff --git a/src/models/security-scheme.ts b/src/models/security-scheme.ts new file mode 100644 index 000000000..d4ab1813e --- /dev/null +++ b/src/models/security-scheme.ts @@ -0,0 +1,18 @@ +import { BaseModel } from './base'; +import { DescriptionMixinInterface, ExtensionsMixinInterface } from './mixins'; +import { OAuthFlowsInterface } from './oauth-flows'; + +export type SecuritySchemaType = 'userPassword' | 'apiKey' | 'X509' | 'symmetricEncryption' | 'asymmetricEncryption' | 'httpApiKey' | 'http' | 'oauth2' | 'openIdConnect' | 'plain' | 'scramSha256' | 'scramSha512' | 'gssapi'; + + +export interface SecuritySchemeInterface extends BaseModel, DescriptionMixinInterface, ExtensionsMixinInterface { + id(): string + hasBearerFormat(): boolean; + bearerFormat(): string | undefined; + openIdConnectUrl(): string; + scheme(): string | undefined; + flows(): OAuthFlowsInterface | undefined; + scopes(): string[]; + type(): SecuritySchemaType; + in(): string | undefined; +} \ No newline at end of file diff --git a/src/models/security-schemes.ts b/src/models/security-schemes.ts new file mode 100644 index 000000000..377919fc1 --- /dev/null +++ b/src/models/security-schemes.ts @@ -0,0 +1,4 @@ +import {Collection} from './collection'; +import { SecuritySchemeInterface } from './security-scheme'; + +export interface SecuritySchemesInterface extends Collection {} \ No newline at end of file diff --git a/src/models/v2/index.ts b/src/models/v2/index.ts index 64a301036..2445a397b 100644 --- a/src/models/v2/index.ts +++ b/src/models/v2/index.ts @@ -7,4 +7,10 @@ export { Extensions as ExtensionsV2, Extension as ExtensionV2 } from './mixins/e export { ExternalDocumentation as ExternalDocumentationV2 } from './mixins/external-docs'; export { Tags as TagsV2, Tag as TagV2 } from './mixins/tags'; export { Server as ServerV2 } from './server'; -export { Servers as ServersV2 } from './servers'; \ No newline at end of file +export { Servers as ServersV2 } from './servers'; +export { SecurityScheme as SecuritySchemeV2 } from './security-scheme'; +export { SecuritySchemes as SecuritySchemesV2 } from './security-schemes'; +export { ServerVariable as ServerVariableV2 } from './server-variable'; +export { ServerVariables as ServerVariablesV2 } from './server-variables'; +export {OAuthFlow as OAuthFlowV2} from './oauth-flow'; +export {OAuthFlows as OAuthFlowsV2} from './oauth-flows'; diff --git a/src/models/v2/oauth-flow.ts b/src/models/v2/oauth-flow.ts new file mode 100644 index 000000000..2891357f9 --- /dev/null +++ b/src/models/v2/oauth-flow.ts @@ -0,0 +1,23 @@ +import { OAuthFlowInterface } from '../oauth-flow'; +import { BaseModel } from '../base'; +import { Mixin } from '../utils'; +import { ExtensionsMixin } from './mixins/extensions'; + +export class OAuthFlow extends Mixin(BaseModel, ExtensionsMixin) implements OAuthFlowInterface { + authorizationUrl(): string | undefined { + return this._json.authorizationUrl; + } + hasRefreshUrl(): boolean { + return !!this._json.refreshUrl; + } + refreshUrl(): string | undefined { + return this._json.refreshUrl; + } + scopes(): Record | undefined { + return this._json.scopes; + } + tokenUrl(): string | undefined { + return this._json.tokenUrl; + } + +} \ No newline at end of file diff --git a/src/models/v2/oauth-flows.ts b/src/models/v2/oauth-flows.ts new file mode 100644 index 000000000..51399a416 --- /dev/null +++ b/src/models/v2/oauth-flows.ts @@ -0,0 +1,39 @@ +import { OAuthFlowsInterface } from '../oauth-flows'; +import { BaseModel } from '../base'; +import { Mixin } from '../utils'; +import { ExtensionsMixin } from './mixins/extensions'; +import { OAuthFlowInterface } from 'models/oauth-flow'; +import { OAuthFlow } from './oauth-flow'; + +export class OAuthFlows extends Mixin(BaseModel, ExtensionsMixin) implements OAuthFlowsInterface { + hasAuthorizationCode(): boolean { + return !!this._json.authorizationCode; + } + authorizationCode(): OAuthFlowInterface | undefined { + if (!this._json.authorizationCode) return undefined; + return new OAuthFlow(this._json.authorizationCode); + } + hasClientCredentials(): boolean { + return !!this._json.clientCredentials; + } + clientCredentials(): OAuthFlowInterface | undefined { + if (!this._json.clientCredentials) return undefined; + return new OAuthFlow(this._json.clientCredentials); + } + hasImplicit(): boolean { + return !!this._json.implicit; + } + implicit(): OAuthFlowInterface | undefined { + if (!this._json.implicit) return undefined; + return new OAuthFlow(this._json.implicit); + } + hasPassword(): boolean { + return !!this._json.password; + } + password(): OAuthFlowInterface | undefined { + if (!this._json.password) return undefined; + return new OAuthFlow(this._json.password); + } + + +} \ No newline at end of file diff --git a/src/models/v2/security-scheme.ts b/src/models/v2/security-scheme.ts new file mode 100644 index 000000000..ce2785970 --- /dev/null +++ b/src/models/v2/security-scheme.ts @@ -0,0 +1,55 @@ +import { BaseModel, ModelMetadata } from '../base'; +import { Mixin } from '../utils'; +import { DescriptionMixin } from './mixins/description'; +import { ExtensionsMixin } from './mixins/extensions'; +import { SecuritySchemaType, SecuritySchemeInterface } from '../security-scheme'; +import { OAuthFlowsInterface } from 'models/oauth-flows'; +import { OAuthFlows } from './oauth-flows'; + +export class SecurityScheme extends Mixin(BaseModel, DescriptionMixin, ExtensionsMixin) implements SecuritySchemeInterface { + constructor( + private readonly _id: string, + _json: Record, + _meta: ModelMetadata = {} as any + ) { + super(_json, _meta); + } + + id(): string { + return this._id; + } + + hasBearerFormat(): boolean { + return !!this._json.bearerFormat; + } + + bearerFormat(): string | undefined { + return this._json.bearerFormat; + } + + openIdConnectUrl(): string { + return this._json.openIdConnectUrl; + } + + scheme(): string | undefined { + return this._json.scheme + } + + flows(): OAuthFlowsInterface | undefined { + if(!this._json.flows) return undefined; + return new OAuthFlows(this._json.flows); + } + + scopes(): string[] { + return this._json.scopes; + } + + type(): SecuritySchemaType { + return this._json.type; + } + + in(): string | undefined { + return this._json.in; + } + +} \ No newline at end of file diff --git a/src/models/v2/security-schemes.ts b/src/models/v2/security-schemes.ts new file mode 100644 index 000000000..500ded280 --- /dev/null +++ b/src/models/v2/security-schemes.ts @@ -0,0 +1,12 @@ +import { SecuritySchemesInterface } from '../security-schemes'; +import { Collection } from '../collection'; +import { SecuritySchemeInterface } from '../security-scheme'; + +export class SecuritySchemes extends Collection implements SecuritySchemesInterface { + get(id: string): SecuritySchemeInterface | undefined { + return this.collections.find(securityScheme => securityScheme.id() === id); + } + has(id: string): boolean { + return this.collections.some(securityScheme => securityScheme.id() === id); + } +} \ No newline at end of file diff --git a/src/models/v2/server.ts b/src/models/v2/server.ts index 53ac4f6a1..165e82a3d 100644 --- a/src/models/v2/server.ts +++ b/src/models/v2/server.ts @@ -47,10 +47,11 @@ export class Server extends Mixin(BaseModel, BindingsMixin, DescriptionMixin, Ex ).map( ([serverVariableName, serverVariable]) => this.createModel( ServerVariable, serverVariable, { - id: serverVariableName, - pointer: `${this._meta.pointer}/variables/${serverVariableName}` + id: serverVariableName, + pointer: `${this._meta.pointer}/variables/${serverVariableName}` } ) )) } + } \ No newline at end of file diff --git a/src/models/v3/index.ts b/src/models/v3/index.ts index c974ffbeb..053a83fa4 100644 --- a/src/models/v3/index.ts +++ b/src/models/v3/index.ts @@ -7,4 +7,10 @@ export { Extensions as ExtensionsV3, Extension as ExtensionV3 } from './mixins/e export { ExternalDocumentation as ExternalDocumentationV3 } from './mixins/external-docs'; export { Tags as TagsV3, Tag as TagV3 } from './mixins/tags'; export { Server as ServerV3 } from './server'; -export { Servers as ServersV3 } from './servers'; \ No newline at end of file +export { Servers as ServersV3 } from './servers'; +export { SecurityScheme as SecuritySchemeV3 } from './security-scheme'; +export { SecuritySchemes as SecuritySchemesV3 } from './security-schemes'; +export { ServerVariable as ServerVariableV3 } from './server-variable'; +export { ServerVariables as ServerVariablesV3 } from './server-variables'; +export {OAuthFlow as OAuthFlowV3} from './oauth-flow'; +export {OAuthFlows as OAuthFlowsV3} from './oauth-flows'; diff --git a/src/models/v3/oauth-flow.ts b/src/models/v3/oauth-flow.ts new file mode 100644 index 000000000..70cb6846f --- /dev/null +++ b/src/models/v3/oauth-flow.ts @@ -0,0 +1,24 @@ + +import { OAuthFlowInterface } from '../oauth-flow'; +import { BaseModel } from '../base'; +import { Mixin } from '../utils'; +import { ExtensionsMixin } from './mixins/extensions'; + +export class OAuthFlow extends Mixin(BaseModel, ExtensionsMixin) implements OAuthFlowInterface { + authorizationUrl(): string | undefined { + return this._json.authorizationUrl; + } + hasRefreshUrl(): boolean { + return !!this._json.refreshUrl; + } + refreshUrl(): string | undefined { + return this._json.refreshUrl; + } + scopes(): Record | undefined { + return this._json.scopes; + } + tokenUrl(): string | undefined { + return this._json.tokenUrl; + } + +} \ No newline at end of file diff --git a/src/models/v3/oauth-flows.ts b/src/models/v3/oauth-flows.ts new file mode 100644 index 000000000..592f49ab7 --- /dev/null +++ b/src/models/v3/oauth-flows.ts @@ -0,0 +1,40 @@ + +import { OAuthFlowsInterface } from '../oauth-flows'; +import { BaseModel } from '../base'; +import { Mixin } from '../utils'; +import { ExtensionsMixin } from './mixins/extensions'; +import { OAuthFlowInterface } from 'models/oauth-flow'; +import { OAuthFlow } from './oauth-flow'; + +export class OAuthFlows extends Mixin(BaseModel, ExtensionsMixin) implements OAuthFlowsInterface { + hasAuthorizationCode(): boolean { + return !!this._json.authorizationCode; + } + authorizationCode(): OAuthFlowInterface | undefined { + if (!this._json.authorizationCode) return undefined; + return new OAuthFlow(this._json.authorizationCode); + } + hasClientCredentials(): boolean { + return !!this._json.clientCredentials; + } + clientCredentials(): OAuthFlowInterface | undefined { + if (!this._json.clientCredentials) return undefined; + return new OAuthFlow(this._json.clientCredentials); + } + hasImplicit(): boolean { + return !!this._json.implicit; + } + implicit(): OAuthFlowInterface | undefined { + if (!this._json.implicit) return undefined; + return new OAuthFlow(this._json.implicit); + } + hasPassword(): boolean { + return !!this._json.password; + } + password(): OAuthFlowInterface | undefined { + if (!this._json.password) return undefined; + return new OAuthFlow(this._json.password); + } + + +} \ No newline at end of file diff --git a/src/models/v3/security-scheme.ts b/src/models/v3/security-scheme.ts new file mode 100644 index 000000000..ce2785970 --- /dev/null +++ b/src/models/v3/security-scheme.ts @@ -0,0 +1,55 @@ +import { BaseModel, ModelMetadata } from '../base'; +import { Mixin } from '../utils'; +import { DescriptionMixin } from './mixins/description'; +import { ExtensionsMixin } from './mixins/extensions'; +import { SecuritySchemaType, SecuritySchemeInterface } from '../security-scheme'; +import { OAuthFlowsInterface } from 'models/oauth-flows'; +import { OAuthFlows } from './oauth-flows'; + +export class SecurityScheme extends Mixin(BaseModel, DescriptionMixin, ExtensionsMixin) implements SecuritySchemeInterface { + constructor( + private readonly _id: string, + _json: Record, + _meta: ModelMetadata = {} as any + ) { + super(_json, _meta); + } + + id(): string { + return this._id; + } + + hasBearerFormat(): boolean { + return !!this._json.bearerFormat; + } + + bearerFormat(): string | undefined { + return this._json.bearerFormat; + } + + openIdConnectUrl(): string { + return this._json.openIdConnectUrl; + } + + scheme(): string | undefined { + return this._json.scheme + } + + flows(): OAuthFlowsInterface | undefined { + if(!this._json.flows) return undefined; + return new OAuthFlows(this._json.flows); + } + + scopes(): string[] { + return this._json.scopes; + } + + type(): SecuritySchemaType { + return this._json.type; + } + + in(): string | undefined { + return this._json.in; + } + +} \ No newline at end of file diff --git a/src/models/v3/security-schemes.ts b/src/models/v3/security-schemes.ts new file mode 100644 index 000000000..500ded280 --- /dev/null +++ b/src/models/v3/security-schemes.ts @@ -0,0 +1,12 @@ +import { SecuritySchemesInterface } from '../security-schemes'; +import { Collection } from '../collection'; +import { SecuritySchemeInterface } from '../security-scheme'; + +export class SecuritySchemes extends Collection implements SecuritySchemesInterface { + get(id: string): SecuritySchemeInterface | undefined { + return this.collections.find(securityScheme => securityScheme.id() === id); + } + has(id: string): boolean { + return this.collections.some(securityScheme => securityScheme.id() === id); + } +} \ No newline at end of file diff --git a/src/models/v3/server.ts b/src/models/v3/server.ts index 4a454b932..ce67bb8bb 100644 --- a/src/models/v3/server.ts +++ b/src/models/v3/server.ts @@ -11,6 +11,7 @@ import { ServerVariablesInterface } from '../server-variables'; import type { ModelMetadata } from "../base"; import type { ServerInterface } from '../server'; + export class Server extends Mixin(BaseModel, BindingsMixin, DescriptionMixin, ExtensionsMixin) implements ServerInterface { constructor( private readonly _id: string, @@ -47,10 +48,11 @@ export class Server extends Mixin(BaseModel, BindingsMixin, DescriptionMixin, Ex ).map( ([serverVariableName, serverVariable]) => this.createModel( ServerVariable, serverVariable, { - id: serverVariableName, - pointer: `${this._meta.pointer}/variables/${serverVariableName}` + id: serverVariableName, + pointer: `${this._meta.pointer}/variables/${serverVariableName}` } ) )) } + } \ No newline at end of file diff --git a/test/models/v2/oauth-flow.ts b/test/models/v2/oauth-flow.ts new file mode 100644 index 000000000..3bddb0e84 --- /dev/null +++ b/test/models/v2/oauth-flow.ts @@ -0,0 +1,34 @@ +import { OAuthFlow } from '../../../src/models/v2/oauth-flow'; + +const flowObject = { + "authorizationUrl": "https://example.com/api/oauth/dialog", + "scopes": { + "write:pets": "modify pets in your account", + "read:pets": "read your pets" + } +} + +const flow = new OAuthFlow(flowObject); +const emptyObject = new OAuthFlow({}); + + +describe('OAuth Flow', function(){ + describe('.authorizationUrl()', function(){ + it('should reutrn undefined if no authorizationUrl present', function(){ + expect(emptyObject.authorizationUrl()).toBeUndefined(); + }) + + it('should return authrozationUrl ', function(){ + expect(flow.authorizationUrl()).toMatch(flowObject.authorizationUrl); + }) + }) + + describe('.scopes()', function() { + it('should return scopes if present', function() { + expect(emptyObject.scopes()).toBeUndefined(); + expect(flow.scopes()['write:pets']).toMatch(flowObject.scopes['write:pets']); + }) + }) + + +}) \ No newline at end of file diff --git a/test/models/v2/oauth-flows.ts b/test/models/v2/oauth-flows.ts new file mode 100644 index 000000000..3f85ab013 --- /dev/null +++ b/test/models/v2/oauth-flows.ts @@ -0,0 +1,36 @@ +import {OAuthFlows} from '../../../src/models/v2/oauth-flows'; +import {OAuthFlow} from '../../../src/models/v2/oauth-flow'; + +const oAuthFlowsObject = { + "implicit": { + "authorizationUrl": "https://example.com/api/oauth/dialog", + "scopes": { + "write:pets": "modify pets in your account", + "read:pets": "read your pets" + } + }, + "authorizationCode": { + "authorizationUrl": "https://example.com/api/oauth/dialog", + "tokenUrl": "https://example.com/api/oauth/token", + "scopes": { + "write:pets": "modify pets in your account", + "read:pets": "read your pets" + } + } +} + +const flows = new OAuthFlows(oAuthFlowsObject); + +describe('OAuth Flows', function() { + describe('.hasImplicit()', function(){ + it('should return true', function(){ + expect(flows.hasImplicit()).toBeTruthy(); + }) + }) + + describe('.implicit()', function() { + it('should return OAuthflow object', function() { + expect(flows.implicit() instanceof OAuthFlow).toBeTruthy(); + }) + }) +}) \ No newline at end of file diff --git a/test/models/v2/security-scheme.spec.ts b/test/models/v2/security-scheme.spec.ts new file mode 100644 index 000000000..e8e8ae590 --- /dev/null +++ b/test/models/v2/security-scheme.spec.ts @@ -0,0 +1,68 @@ +import { SecurityScheme } from '../../../src/models/v2/security-scheme'; +import {OAuthFlows} from '../../../src/models/v2/oauth-flows'; + +const doc1 = { + type: 'http', + in: 'header', + scheme: 'bearer', + bearerFormat: 'JWT', + openIdConnectUrl: 'https://server.com/.well-known/openid-configuration', + flows: { + implicit: { + authorizationUrl: "https://example.com/api/oauth/dialog", + scopes: { + "write:pets": "modify pets in your account", + "read:pets": "read your pets" + } + } + } +} + +const sc1 = new SecurityScheme('api_key', doc1); +const emptyItem = new SecurityScheme('', {}); + +describe('Security Scheme', function () { + describe('.id()', function () { + it('should return name if present', function () { + expect(sc1.id()).toMatch('api_key'); + }); + }) + + describe('.type()', function () { + it('should return type when it is present', function () { + expect(sc1.type()).toMatch(doc1.type); + }) + }) + + describe('.hasBearerFormat()', function () { + it('should return true if bearerFormat is present', function () { + expect(sc1.hasBearerFormat()).toBeTruthy(); + }) + + it('should return false if bearerFormat is not present', function () { + expect(emptyItem.hasBearerFormat()).toBeFalsy(); + }) + }) + + describe('.in()', function () { + it('should return in if present', function () { + expect(sc1.in()).toMatch(doc1.in); + expect(emptyItem.in()).toBeUndefined(); + }) + }) + + describe('.openIdConnectUrl()', function () { + it('should return openIdConnectUrl value', function () { + expect(sc1.openIdConnectUrl()).toMatch(doc1.openIdConnectUrl); + }) + }) + describe('.flows()', function () { + it('should return undefined if flow object is not present', function () { + expect(emptyItem.flows()).toBeUndefined(); + }); + + it('should return OAuthFlows object', function() { + expect(sc1.flows() instanceof OAuthFlows).toBeTruthy(); + }) + }) +}) diff --git a/test/models/v2/server.spec.ts b/test/models/v2/server.spec.ts index b1f04a4db..39dcc7f7f 100644 --- a/test/models/v2/server.spec.ts +++ b/test/models/v2/server.spec.ts @@ -1,7 +1,7 @@ import { Server } from '../../../src/models/v2/server'; -import {ServerVariables} from '../../../src/models/v2/server-variables'; +import { ServerVariables } from '../../../src/models/v2/server-variables'; -import { +import { assertDescriptionMixinInheritance, assertExtensionsMixinInheritance, } from './mixins/inheritance'; @@ -20,7 +20,7 @@ const doc = { } }; const docItem = new Server('development', doc.development); -const emptyItem = new Server('',{}); +const emptyItem = new Server('', {}); describe('Server Model', function () { describe('.id()', function () { @@ -61,13 +61,13 @@ describe('Server Model', function () { }); }); - describe('.servers()', function(){ - it('should return ServerVariables object', function(){ + describe('.servers()', function () { + it('should return ServerVariables object', function () { expect(docItem.variables() instanceof ServerVariables).toBeTruthy(); }) }) - describe('mixins inheritance', function() { + describe('mixins inheritance', function () { assertDescriptionMixinInheritance(Server); assertExtensionsMixinInheritance(Server); });