diff --git a/logical/framework/openapi.go b/logical/framework/openapi.go index 2a14e07c68..237311f296 100644 --- a/logical/framework/openapi.go +++ b/logical/framework/openapi.go @@ -177,12 +177,14 @@ var OASStdRespNoContent = &OASResponse{ // Both "(leases/)?renew" and "(/(?P.+))?" formats are detected var optRe = regexp.MustCompile(`(?U)\([^(]*\)\?|\(/\(\?P<[^(]*\)\)\?`) -var reqdRe = regexp.MustCompile(`\(?\?P<(\w+)>[^)]*\)?`) // Capture required parameters, e.g. "(?Pregex)" -var altRe = regexp.MustCompile(`\((.*)\|(.*)\)`) // Capture alternation elements, e.g. "(raw/?$|raw/(?P.+))" -var pathFieldsRe = regexp.MustCompile(`{(\w+)}`) // Capture OpenAPI-style named parameters, e.g. "lookup/{urltoken}", -var cleanCharsRe = regexp.MustCompile("[()^$?]") // Set of regex characters that will be stripped during cleaning -var cleanSuffixRe = regexp.MustCompile(`/\?\$?$`) // Path suffix patterns that will be stripped during cleaning -var wsRe = regexp.MustCompile(`\s+`) // Match whitespace, to be compressed during cleaning +var reqdRe = regexp.MustCompile(`\(?\?P<(\w+)>[^)]*\)?`) // Capture required parameters, e.g. "(?Pregex)" +var altRe = regexp.MustCompile(`\((.*)\|(.*)\)`) // Capture alternation elements, e.g. "(raw/?$|raw/(?P.+))" +var pathFieldsRe = regexp.MustCompile(`{(\w+)}`) // Capture OpenAPI-style named parameters, e.g. "lookup/{urltoken}", +var cleanCharsRe = regexp.MustCompile("[()^$?]") // Set of regex characters that will be stripped during cleaning +var cleanSuffixRe = regexp.MustCompile(`/\?\$?$`) // Path suffix patterns that will be stripped during cleaning +var wsRe = regexp.MustCompile(`\s+`) // Match whitespace, to be compressed during cleaning +var altFieldsGroupRe = regexp.MustCompile(`\(\?P<\w+>\w+(\|\w+)+\)`) // Match named groups that limit options, e.g. "(?a|b|c)" +var altFieldsRe = regexp.MustCompile(`\w+(\|\w+)+`) // Match an options set, e.g. "a|b|c" // documentPaths parses all paths in a framework.Backend into OpenAPI paths. func documentPaths(backend *Backend, doc *OASDocument) error { @@ -446,9 +448,13 @@ func expandPattern(pattern string) []string { if start != -1 && end != -1 && end > start { regexToRemove = base[start+1 : end] } - pattern = strings.Replace(pattern, regexToRemove, "", -1) + // Simplify named fields that have limited options, e.g. (?Pa|b|c) -> (.+) + pattern = altFieldsGroupRe.ReplaceAllStringFunc(pattern, func(s string) string { + return altFieldsRe.ReplaceAllString(s, ".+") + }) + // Initialize paths with the original pattern or the halves of an // alternation, which is also present in some patterns. matches := altRe.FindAllStringSubmatch(pattern, -1) diff --git a/logical/framework/openapi_test.go b/logical/framework/openapi_test.go index 1c2ebd7aaa..99f4c299c1 100644 --- a/logical/framework/openapi_test.go +++ b/logical/framework/openapi_test.go @@ -74,6 +74,18 @@ func TestOpenAPI_Regex(t *testing.T) { t.Fatalf("Expected nil match (%s), got %+v", input, matches) } }) + t.Run("Alternation Fields", func(t *testing.T) { + input := `/foo/bar/(?Pauth|database|secret)/(?Pa|b)` + + act := altFieldsGroupRe.ReplaceAllStringFunc(input, func(s string) string { + return altFieldsRe.ReplaceAllString(s, ".+") + }) + + exp := "/foo/bar/(?P.+)/(?P.+)" + if act != exp { + t.Fatalf("Replace error. Expected %s, got %v", exp, act) + } + }) t.Run("Path fields", func(t *testing.T) { input := `/foo/bar/{inner}/baz/{outer}` @@ -180,6 +192,12 @@ func TestOpenAPI_ExpandPattern(t *testing.T) { "verify/{name}", "verify/{name}/{urlalgorithm}", }}, + {"^plugins/catalog/(?Pauth|database|secret)/(?P.+)$", []string{ + "plugins/catalog/{type}/{name}", + }}, + {"^plugins/catalog/(?Pauth|database|secret)/?$", []string{ + "plugins/catalog/{type}", + }}, } for i, test := range tests {