From 3b157dab6b1336ba4cac4a4afbaef771b03746e6 Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Thu, 22 Jun 2017 20:12:01 -0500 Subject: [PATCH 1/2] Addresses issue #283 by adding oauth2 security scopes to operation docs --- lib/components/AuthScopes/auth-scopes.html | 12 ++ lib/components/AuthScopes/auth-scopes.scss | 32 ++++++ lib/components/AuthScopes/auth-scopes.ts | 32 ++++++ lib/components/Operation/operation.html | 1 + lib/components/index.ts | 5 +- lib/utils/spec-manager.ts | 36 ++++++ lib/vendor.ts | 2 +- package.json | 2 +- .../schema-mgr-operation-security.json | 108 ++++++++++++++++++ tests/unit/SpecManager.spec.ts | 27 +++++ 10 files changed, 253 insertions(+), 4 deletions(-) create mode 100644 lib/components/AuthScopes/auth-scopes.html create mode 100644 lib/components/AuthScopes/auth-scopes.scss create mode 100644 lib/components/AuthScopes/auth-scopes.ts create mode 100644 tests/schemas/schema-mgr-operation-security.json diff --git a/lib/components/AuthScopes/auth-scopes.html b/lib/components/AuthScopes/auth-scopes.html new file mode 100644 index 00000000..e1a4e3a8 --- /dev/null +++ b/lib/components/AuthScopes/auth-scopes.html @@ -0,0 +1,12 @@ + + + ! +
+ OAuth2 Scopes +
+

{{scope.name}}

+

{{scope.description}}

+
+
+
+
diff --git a/lib/components/AuthScopes/auth-scopes.scss b/lib/components/AuthScopes/auth-scopes.scss new file mode 100644 index 00000000..9d1b4211 --- /dev/null +++ b/lib/components/AuthScopes/auth-scopes.scss @@ -0,0 +1,32 @@ +$hint-color: #e53935; + +.oauth-scopes-header { + font-weight: bold; +} + +.oauth-scope { + padding-top: 1.2em; +} + +.oauth-scope-description { + font-style: italic; + line-height: 0.2em; +} + +.oauth-scopes { + text-align: left; + white-space: nowrap; +} + +.oauth-scopes-hint { + width: 1.2em; + text-align: center; + border-radius: 50%; + vertical-align: middle; + color: $hint-color; + line-height: 1.2; + text-transform: none; + cursor: help; + border: 1px solid $hint-color; + float: right; +} diff --git a/lib/components/AuthScopes/auth-scopes.ts b/lib/components/AuthScopes/auth-scopes.ts new file mode 100644 index 00000000..0573b3c3 --- /dev/null +++ b/lib/components/AuthScopes/auth-scopes.ts @@ -0,0 +1,32 @@ +'use strict'; +import { Component, Input, ChangeDetectionStrategy, OnInit } from '@angular/core'; +import { BaseComponent, SpecManager } from '../base'; +import { SchemaHelper } from '../../services/schema-helper.service'; +import { ComponentParser } from '../../services/component-parser.service'; + +@Component({ + selector: 'auth-scopes', + templateUrl: './auth-scopes.html', + styleUrls: ['./auth-scopes.css'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class AuthScopes extends BaseComponent implements OnInit { + @Input() pointer:string; + + public scopes: any; + + constructor(specMgr:SpecManager) { + super(specMgr); + } + + init() { + let theScopes = this.specMgr.getOperationScopes(this.pointer); + if (theScopes.length) { + this.scopes = {scopes: theScopes} + } + } + + ngOnInit() { + this.preinit(); + } +} diff --git a/lib/components/Operation/operation.html b/lib/components/Operation/operation.html index 4fa9a345..05d9f794 100644 --- a/lib/components/Operation/operation.html +++ b/lib/components/Operation/operation.html @@ -2,6 +2,7 @@

{{operation.summary}} +

diff --git a/lib/components/index.ts b/lib/components/index.ts index 719d20d6..fe5a48e5 100644 --- a/lib/components/index.ts +++ b/lib/components/index.ts @@ -8,6 +8,7 @@ import { ParamsList } from './ParamsList/params-list'; import { RequestSamples } from './RequestSamples/request-samples'; import { ResponsesList } from './ResponsesList/responses-list'; import { ResponsesSamples } from './ResponsesSamples/responses-samples'; +import { AuthScopes} from './AuthScopes/auth-scopes'; import { SchemaSample } from './SchemaSample/schema-sample'; import { SideMenu, SideMenuItems } from './SideMenu/side-menu'; import { OperationsList } from './OperationsList/operations-list'; @@ -23,10 +24,10 @@ import { Redoc } from './Redoc/redoc'; export const REDOC_DIRECTIVES = [ ApiInfo, ApiLogo, JsonSchema, JsonSchemaLazy, ParamsList, RequestSamples, ResponsesList, - ResponsesSamples, SchemaSample, SideMenu, OperationsList, Operation, Warnings, Redoc, SecurityDefinitions, + ResponsesSamples, AuthScopes, SchemaSample, SideMenu, OperationsList, Operation, Warnings, Redoc, SecurityDefinitions, LoadingBar, SideMenuItems, RedocSearch, ExternalDocs, EndpointLink ]; export { ApiInfo, ApiLogo, JsonSchema, JsonSchemaLazy, ParamsList, RequestSamples, ResponsesList, -ResponsesSamples, SchemaSample, SideMenu, OperationsList, Operation, Warnings, Redoc, SecurityDefinitions, +ResponsesSamples, AuthScopes, SchemaSample, SideMenu, OperationsList, Operation, Warnings, Redoc, SecurityDefinitions, LoadingBar, SideMenuItems, ExternalDocs, EndpointLink }; diff --git a/lib/utils/spec-manager.ts b/lib/utils/spec-manager.ts index 084cf22b..66ae183b 100644 --- a/lib/utils/spec-manager.ts +++ b/lib/utils/spec-manager.ts @@ -137,6 +137,42 @@ export class SpecManager { return obj; } + getOperationScopes(operationPtr:string) { + let that = this; + function getSecurityDefinition(name:string, scope:string) { + let secDefs = that._schema.securityDefinitions; + if (secDefs[name] && secDefs[name].type === "oauth2") { + let availScopes = secDefs[name].scopes + if (availScopes && availScopes[scope]) { + return availScopes[scope]; + } + } + return null; + } + + let securityParams = this.byPointer(operationPtr) || []; + let scopes = []; + + // only support one security item per operation for now + let base = securityParams[0]; + + if (base && base[Object.keys(base)[0]]) { + let scopeObj = base[Object.keys(base)[0]]; + let authName = Object.keys(base)[0]; + Object.keys(scopeObj).forEach(key => { + let val = scopeObj[key]; + let desc = getSecurityDefinition(authName, val); + + // don't add if the security obj doesn't exist in the security definitions + if (desc) { + scopes.push({name: val, description: desc}); + } + }); + } + + return scopes; + } + getOperationParams(operationPtr:string):SwaggerParameter[] { /* inject JsonPointer into array elements */ function injectPointers(array:SwaggerParameter[], root) { diff --git a/lib/vendor.ts b/lib/vendor.ts index edbd8669..ebdc2515 100644 --- a/lib/vendor.ts +++ b/lib/vendor.ts @@ -22,7 +22,7 @@ import 'prismjs/components/prism-markup.js'; // xml import 'dropkickjs/build/css/dropkick.css'; import 'prismjs/themes/prism-dark.css'; -import 'hint.css/hint.base.css'; +import 'html-hint/dist/html-hint.css'; if (!IS_PRODUCTION) { require('@angular/platform-browser'); diff --git a/package.json b/package.json index 91ef8ee2..a4a1e5fb 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,7 @@ "css-loader": "^0.28.1", "deploy-to-gh-pages": "^1.3.3", "dropkickjs": "^2.1.10", - "hint.css": "^2.5.0", + "html-hint": "^0.2.4", "http-server": "^0.10.0", "https-browserify": "^1.0.0", "istanbul-instrumenter-loader": "^2.0.0", diff --git a/tests/schemas/schema-mgr-operation-security.json b/tests/schemas/schema-mgr-operation-security.json new file mode 100644 index 00000000..ed9e613b --- /dev/null +++ b/tests/schemas/schema-mgr-operation-security.json @@ -0,0 +1,108 @@ +{ + "swagger": "2.0", + "info": { + "version": "1.0.0", + "title": "Test schema" + }, + "host": "petstore.swagger.io", + "basePath": "/v2/", + "parameters": { + "extparam": { + "name": "extParam", + "in": "query", + "type": "integer" + } + }, + "securityDefinitions": { + "petstore_auth": { + "type": "oauth2", + "authorizationUrl": "http://petstore.swagger.io/api/oauth/dialog", + "flow": "implicit", + "scopes": { + "write:pets": "modify pets in your account", + "read:pets": "read your pets" + } + }, + "github_auth": { + "type": "oauth2", + "authorizationUrl": "https://api.github.com/authorize", + "flow": "implicit", + "scopes": { + "write:account": "modify your account", + "read:account": "read your account" + } + }, + "api_key": { + "type": "apiKey", + "name": "api_key", + "in": "header" + } + }, + "paths": { + "test1": { + "parameters": [ + { + "name": "pathParam", + "in": "path", + "type": "string" + } + ], + "get": { + "summary": "test get", + "parameters": [ + { + "name": "operationParam", + "in": "path", + "type": "string" + } + ], + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "test2": { + "parameters": [ + { "$ref": "#/parameters/extparam" } + ], + "get": { + "summary": "test get", + "parameters": [ + { + "name": "operationParam", + "in": "path", + "type": "string" + } + ] + } + }, + "test3": { + "get": { + "summary": "test get", + "parameters": [ + { "$ref": "#/parameters/extparam" } + ], + "security": [ + { + "github_auth": [ + "write:account" + ] + } + ] + } + }, + "test4": { + "parameters": { + "$ref": "#/parameters/extparam" + }, + "get": { + "summary": "test get" + } + } + } +} diff --git a/tests/unit/SpecManager.spec.ts b/tests/unit/SpecManager.spec.ts index 5872c32e..c8841cb7 100644 --- a/tests/unit/SpecManager.spec.ts +++ b/tests/unit/SpecManager.spec.ts @@ -105,6 +105,33 @@ describe('Utils', () => { }); }); + describe('getOperationScopes method', () => { + beforeEach(function (done) { + specMgr.load('/tests/schemas/schema-mgr-operation-security.json').then(done, done.fail); + }); + + it('should handle operation oauth2 scopes', () => { + let scopes = specMgr.getOperationScopes('/paths/test1/get/security'); + scopes.length.should.be.equal(2); + scopes[0].name.should.be.equal('write:pets'); + scopes[0].description.should.be.equal('modify pets in your account'); + scopes[1].name.should.be.equal('read:pets'); + scopes[1].description.should.be.equal('read your pets'); + }); + + it('should handle operation scopes when multiple definitions are used', () => { + let scopes = specMgr.getOperationScopes('/paths/test3/get/security'); + scopes.length.should.be.equal(1); + scopes[0].name.should.be.equal('write:account'); + scopes[0].description.should.be.equal('modify your account');\ + }); + + it('should handle the case when no security is present', () => { + let scopes = specMgr.getOperationScopes('/paths/test2/get/security'); + scopes.length.should.be.equal(0); + }); + }); + describe('getOperationParams method', () => { beforeEach((done:any) => { specMgr.load('/tests/schemas/schema-mgr-operationParams.json').then(done, done.fail); From addd10b762dafc0f2fe9a81e0df5fe8362c4e209 Mon Sep 17 00:00:00 2001 From: Jim Anderson Date: Thu, 22 Jun 2017 20:45:19 -0500 Subject: [PATCH 2/2] fix lint errors --- lib/utils/spec-manager.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/utils/spec-manager.ts b/lib/utils/spec-manager.ts index 66ae183b..14f08e11 100644 --- a/lib/utils/spec-manager.ts +++ b/lib/utils/spec-manager.ts @@ -141,8 +141,8 @@ export class SpecManager { let that = this; function getSecurityDefinition(name:string, scope:string) { let secDefs = that._schema.securityDefinitions; - if (secDefs[name] && secDefs[name].type === "oauth2") { - let availScopes = secDefs[name].scopes + if (secDefs[name] && secDefs[name].type === 'oauth2') { + let availScopes = secDefs[name].scopes; if (availScopes && availScopes[scope]) { return availScopes[scope]; } @@ -162,7 +162,7 @@ export class SpecManager { Object.keys(scopeObj).forEach(key => { let val = scopeObj[key]; let desc = getSecurityDefinition(authName, val); - + // don't add if the security obj doesn't exist in the security definitions if (desc) { scopes.push({name: val, description: desc});