diff --git a/operation.go b/operation.go index 8583f5f97..edf251002 100644 --- a/operation.go +++ b/operation.go @@ -721,6 +721,11 @@ func (operation *Operation) ParseRouterComment(commentLine string) error { // ParseSecurityComment parses comment for given `security` comment string. func (operation *Operation) ParseSecurityComment(commentLine string) error { + if len(commentLine) == 0 { + operation.Security = []map[string][]string{} + return nil + } + var ( securityMap = make(map[string][]string) securitySource = commentLine[strings.Index(commentLine, "@Security")+1:] diff --git a/parser.go b/parser.go index 8afbb5f48..4db4a96f5 100644 --- a/parser.go +++ b/parser.go @@ -569,6 +569,9 @@ func parseGeneralAPIInfo(parser *Parser, comments []string) error { parser.swagger.SecurityDefinitions[value] = scheme + case securityAttr: + parser.swagger.Security = append(parser.swagger.Security, parseSecurity(value)) + case "@query.collection.format": parser.collectionFormatInQuery = TransToValidCollectionFormat(value) @@ -768,6 +771,34 @@ func parseSecAttributes(context string, lines []string, index *int) (*spec.Secur return scheme, nil } +func parseSecurity(commentLine string) map[string][]string { + securityMap := make(map[string][]string) + + for _, securityOption := range strings.Split(commentLine, "||") { + securityOption = strings.TrimSpace(securityOption) + + left, right := strings.Index(securityOption, "["), strings.Index(securityOption, "]") + + if !(left == -1 && right == -1) { + scopes := securityOption[left+1 : right] + + var options []string + + for _, scope := range strings.Split(scopes, ",") { + options = append(options, strings.TrimSpace(scope)) + } + + securityKey := securityOption[0:left] + securityMap[securityKey] = append(securityMap[securityKey], options...) + } else { + securityKey := strings.TrimSpace(securityOption) + securityMap[securityKey] = []string{} + } + } + + return securityMap +} + func initIfEmpty(license *spec.License) *spec.License { if license == nil { return new(spec.License) diff --git a/parser_test.go b/parser_test.go index 55b287d6f..71f360129 100644 --- a/parser_test.go +++ b/parser_test.go @@ -2157,6 +2157,21 @@ func TestParseTypeOverrides(t *testing.T) { assert.Equal(t, string(expected), string(b)) } +func TestGlobalSecurity(t *testing.T) { + t.Parallel() + + searchDir := "testdata/global_security" + p := New() + err := p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth) + assert.NoError(t, err) + + expected, err := os.ReadFile(filepath.Join(searchDir, "expected.json")) + assert.NoError(t, err) + + b, _ := json.MarshalIndent(p.swagger, "", " ") + assert.Equal(t, string(expected), string(b)) +} + func TestParseNested(t *testing.T) { t.Parallel() diff --git a/testdata/global_security/api/api.go b/testdata/global_security/api/api.go new file mode 100644 index 000000000..8b8741c57 --- /dev/null +++ b/testdata/global_security/api/api.go @@ -0,0 +1,34 @@ +package api + +import ( + "net/http" +) + +// @Summary default security +// @Success 200 +// @Router /testapi/application [get] +func GetApplication(w http.ResponseWriter, r *http.Request) {} + +// @Summary no security +// @Security +// @Success 200 +// @Router /testapi/nosec [get] +func GetNoSec(w http.ResponseWriter, r *http.Request) {} + +// @Summary basic security +// @Security BasicAuth +// @Success 200 +// @Router /testapi/basic [get] +func GetBasic(w http.ResponseWriter, r *http.Request) {} + +// @Summary oauth2 write +// @Security OAuth2Application[write] +// @Success 200 +// @Router /testapi/oauth/write [get] +func GetOAuthWrite(w http.ResponseWriter, r *http.Request) {} + +// @Summary oauth2 admin +// @Security OAuth2Application[admin] +// @Success 200 +// @Router /testapi/oauth/admin [get] +func GetOAuthAdmin(w http.ResponseWriter, r *http.Request) {} diff --git a/testdata/global_security/expected.json b/testdata/global_security/expected.json new file mode 100644 index 000000000..7505df733 --- /dev/null +++ b/testdata/global_security/expected.json @@ -0,0 +1,105 @@ +{ + "swagger": "2.0", + "info": { + "title": "Swagger Example API", + "contact": {}, + "version": "1.0" + }, + "paths": { + "/testapi/application": { + "get": { + "summary": "default security", + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/testapi/basic": { + "get": { + "security": [ + { + "BasicAuth": [] + } + ], + "summary": "basic security", + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/testapi/nosec": { + "get": { + "security": [], + "summary": "no security", + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/testapi/oauth/admin": { + "get": { + "security": [ + { + "OAuth2Application": [ + "admin" + ] + } + ], + "summary": "oauth2 admin", + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/testapi/oauth/write": { + "get": { + "security": [ + { + "OAuth2Application": [ + "write" + ] + } + ], + "summary": "oauth2 write", + "responses": { + "200": { + "description": "OK" + } + } + } + } + }, + "securityDefinitions": { + "APIKeyAuth": { + "type": "apiKey", + "name": "Authorization", + "in": "header" + }, + "BasicAuth": { + "type": "basic" + }, + "OAuth2Application": { + "type": "oauth2", + "flow": "application", + "tokenUrl": "https://example.com/oauth/token", + "scopes": { + "admin": " Grants read and write access to administrative information", + "write": " Grants write access" + } + } + }, + "security": [ + { + "APIKeyAuth": [], + "OAuth2Application": [] + } + ] +} \ No newline at end of file diff --git a/testdata/global_security/main.go b/testdata/global_security/main.go new file mode 100644 index 000000000..83484bac8 --- /dev/null +++ b/testdata/global_security/main.go @@ -0,0 +1,18 @@ +package global_security + +// @title Swagger Example API +// @version 1.0 + +// @securityDefinitions.apikey APIKeyAuth +// @in header +// @name Authorization + +// @securityDefinitions.basic BasicAuth + +// @securityDefinitions.oauth2.application OAuth2Application +// @tokenUrl https://example.com/oauth/token +// @scope.write Grants write access +// @scope.admin Grants read and write access to administrative information + +// @security APIKeyAuth || OAuth2Application +func main() {}