Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions gen/internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,29 @@ func TestCoerceScalarArrayTable(t *testing.T) {
}
}

func TestParseSecurityReqTable(t *testing.T) {
cases := []struct {
want map[string][]string
in string
}{
{map[string][]string{"ApiKeyAuth": nil}, "ApiKeyAuth"},
{map[string][]string{"OAuth2Application": {"write", "admin"}}, "OAuth2Application[write, admin]"},
// && combines schemes into a single requirement (all required together)
{map[string][]string{"ApiKeyAuth": nil, "BearerAuth": nil}, "ApiKeyAuth && BearerAuth"},
{map[string][]string{"OAuth2Application": {"write"}, "ApiKeyAuth": nil}, "OAuth2Application[write] && ApiKeyAuth"},
{map[string][]string{"A": nil, "B": nil}, " A && B "}, // whitespace tolerated
{map[string][]string{"A": nil}, "A && "}, // empty part skipped
{nil, "[write]"}, // malformed: missing scheme name
{nil, ""}, // no schemes: nil, never {} ("no auth")
{nil, " && "}, // separators only: nil as well
}
Comment on lines +128 to +132

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated

for _, c := range cases {
if got := parseSecurityReq(c.in); !reflect.DeepEqual(got, c.want) {
t.Errorf("parseSecurityReq(%q) = %#v, want %#v", c.in, got, c.want)
}
}
}

Comment on lines +133 to +139

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated

func TestApplyValidationRulesTable(t *testing.T) {
str := &Schema{Type: []string{"string"}}
applyValidationRules(str, "min=3,max=10,len=4")
Expand Down Expand Up @@ -160,6 +183,33 @@ func TestApplyValidationRulesTable(t *testing.T) {
applyValidationRules(nil, "min=1")
}

func TestMalformedSecurityEmitsNoRequirement(t *testing.T) {
// A nil/empty parse result must not be appended: a {} requirement would
// mean "no auth required". Guards live in applySecurity and parseOperation.
api := &API{SecuritySchemes: map[string]SecurityScheme{}, Extensions: map[string]any{}}
parseGeneral(parseCommentGroup(`@title T
@securitydefinitions.apikey K
@in header
@name Authorization
@security [write]
@security K`), api)
if len(api.Security) != 1 {
t.Fatalf("document security must keep only the valid requirement: %#v", api.Security)
}
if _, ok := api.Security[0]["K"]; !ok || len(api.Security[0]) != 1 {
t.Fatalf("document security requirement must reference exactly scheme K: %#v", api.Security[0])
}

res := &resolver{src: &source{}, schemas: map[string]*Schema{}}
op := parseOperation(parseCommentGroup(`@Router /x [get]
@Summary X
@Security [write]
@Security K`), res, refCtx{})
if len(op.Security) != 1 {
t.Fatalf("operation security must keep only the valid requirement: %#v", op.Security)
}
}

func TestSchemeContextEndsAtNonSecurityDirective(t *testing.T) {
api := &API{SecuritySchemes: map[string]SecurityScheme{}, Extensions: map[string]any{}}
parseGeneral(parseCommentGroup(`@title T
Expand Down
43 changes: 31 additions & 12 deletions gen/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,9 @@ func (p *generalParser) applySecurity(d directive) bool {
case d.name == "security":
// document-level security requirement — not a scheme attribute, so
// it ends the scheme section like any other non-scheme directive
p.api.Security = append(p.api.Security, parseSecurityReq(d.args))
if req := parseSecurityReq(d.args); len(req) > 0 {
p.api.Security = append(p.api.Security, req)
}
p.lastScheme = nil
case p.lastScheme == nil:
return false
Expand Down Expand Up @@ -295,7 +297,9 @@ func parseOperation(ds []directive, res *resolver, ctx refCtx) Operation {
case "success", "failure", "response":
op.Responses = append(op.Responses, parseResponses(d.args, res, ctx)...)
case "security":
op.Security = append(op.Security, parseSecurityReq(d.args))
if req := parseSecurityReq(d.args); len(req) > 0 {
op.Security = append(op.Security, req)
}
case "header":
attachHeader(&op, d.args)
case "externaldocs.url":
Expand Down Expand Up @@ -401,19 +405,34 @@ func splitMimeList(args string) []string {

func parseSecurityReq(args string) map[string][]string {
req := map[string][]string{}
// "OAuth2Application[write, admin]" or "ApiKeyAuth"
name := args
var scopes []string
if open := strings.IndexByte(args, '['); open >= 0 {
name = strings.TrimSpace(args[:open])
inner := strings.Trim(args[open:], "[] ")
for s := range strings.SplitSeq(inner, ",") {
if s = strings.TrimSpace(s); s != "" {
scopes = append(scopes, s)
// One or more schemes combined with && (all required together, swag syntax):
// "OAuth2Application[write, admin]", "ApiKeyAuth" or "ApiKeyAuth && BearerAuth".
for part := range strings.SplitSeq(args, "&&") {
part = strings.TrimSpace(part)
if part == "" {
Comment on lines +408 to +412

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated

continue
}
name := part
var scopes []string
if open := strings.IndexByte(part, '['); open >= 0 {
name = strings.TrimSpace(part[:open])
inner := strings.Trim(part[open:], "[] ")
for s := range strings.SplitSeq(inner, ",") {
if s = strings.TrimSpace(s); s != "" {
scopes = append(scopes, s)
}
}
}
if name == "" {
continue // malformed scope-only part like "[write]" - no scheme to require
}
req[name] = scopes
}
Comment on lines +426 to +430

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated

if len(req) == 0 {
// An empty Security Requirement Object ({}) would mean "no auth
// required" - never emit one for a malformed annotation.
return nil
}
req[name] = scopes
return req
Comment on lines 430 to 436

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated

}

Expand Down
Loading