-
Notifications
You must be signed in to change notification settings - Fork 0
feat: manage double security #5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 | ||
| } | ||
| 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
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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") | ||
|
|
@@ -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 | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 | ||
|
|
@@ -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": | ||
|
|
@@ -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
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. updated |
||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updated