Addresses issue #283 by adding oauth2 security scopes to operation docs

This commit is contained in:
Jim Anderson 2017-06-22 20:12:01 -05:00
parent cfc14c8c7a
commit 3b157dab6b
10 changed files with 253 additions and 4 deletions

View File

@ -0,0 +1,12 @@
<ng-template [ngIf]="scopes">
<span class="hint--html hint--bottom-left hint--large oauth-scopes-hint">
!
<div class="oauth-scopes hint__content">
<span class="oauth-scopes-header">OAuth2 Scopes</span>
<div *ngFor="let scope of scopes.scopes" class="oauth-scope">
<p class="oauth-scope-name"> {{scope.name}} </p>
<p class="oauth-scope-description"> {{scope.description}} </p>
</div>
</div>
</span>
</ng-template>

View File

@ -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;
}

View File

@ -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();
}
}

View File

@ -2,6 +2,7 @@
<div class="operation-content">
<h2 class="operation-header sharable-header">
<a class="share-link" href="#{{operation.anchor}}"></a>{{operation.summary}}
<auth-scopes pointer="{{pointer}}/security"> </auth-scopes>
</h2>
<endpoint-link *ngIf="pathInMiddlePanel"
[verb]="operation.verb" [path]="operation.path"> </endpoint-link>

View File

@ -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 };

View File

@ -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) {

View File

@ -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');

View File

@ -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",

View File

@ -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"
}
}
}
}

View File

@ -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);