Use vendored go-swagger (#8087)
* Use vendored go-swagger * vendor go-swagger * revert un wanteed change * remove un-needed GO111MODULE * Update Makefile Co-Authored-By: techknowlogick <matti@mdranta.net>
This commit is contained in:
parent
4cb1bdddc8
commit
9fe4437bda
686 changed files with 143379 additions and 17 deletions
477
vendor/github.com/go-openapi/runtime/middleware/router.go
generated
vendored
Normal file
477
vendor/github.com/go-openapi/runtime/middleware/router.go
generated
vendored
Normal file
|
@ -0,0 +1,477 @@
|
|||
// Copyright 2015 go-swagger maintainers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
fpath "path"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/go-openapi/runtime/security"
|
||||
|
||||
"github.com/go-openapi/analysis"
|
||||
"github.com/go-openapi/errors"
|
||||
"github.com/go-openapi/loads"
|
||||
"github.com/go-openapi/runtime"
|
||||
"github.com/go-openapi/runtime/middleware/denco"
|
||||
"github.com/go-openapi/spec"
|
||||
"github.com/go-openapi/strfmt"
|
||||
)
|
||||
|
||||
// RouteParam is a object to capture route params in a framework agnostic way.
|
||||
// implementations of the muxer should use these route params to communicate with the
|
||||
// swagger framework
|
||||
type RouteParam struct {
|
||||
Name string
|
||||
Value string
|
||||
}
|
||||
|
||||
// RouteParams the collection of route params
|
||||
type RouteParams []RouteParam
|
||||
|
||||
// Get gets the value for the route param for the specified key
|
||||
func (r RouteParams) Get(name string) string {
|
||||
vv, _, _ := r.GetOK(name)
|
||||
if len(vv) > 0 {
|
||||
return vv[len(vv)-1]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetOK gets the value but also returns booleans to indicate if a key or value
|
||||
// is present. This aids in validation and satisfies an interface in use there
|
||||
//
|
||||
// The returned values are: data, has key, has value
|
||||
func (r RouteParams) GetOK(name string) ([]string, bool, bool) {
|
||||
for _, p := range r {
|
||||
if p.Name == name {
|
||||
return []string{p.Value}, true, p.Value != ""
|
||||
}
|
||||
}
|
||||
return nil, false, false
|
||||
}
|
||||
|
||||
// NewRouter creates a new context aware router middleware
|
||||
func NewRouter(ctx *Context, next http.Handler) http.Handler {
|
||||
if ctx.router == nil {
|
||||
ctx.router = DefaultRouter(ctx.spec, ctx.api)
|
||||
}
|
||||
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
if _, rCtx, ok := ctx.RouteInfo(r); ok {
|
||||
next.ServeHTTP(rw, rCtx)
|
||||
return
|
||||
}
|
||||
|
||||
// Not found, check if it exists in the other methods first
|
||||
if others := ctx.AllowedMethods(r); len(others) > 0 {
|
||||
ctx.Respond(rw, r, ctx.analyzer.RequiredProduces(), nil, errors.MethodNotAllowed(r.Method, others))
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Respond(rw, r, ctx.analyzer.RequiredProduces(), nil, errors.NotFound("path %s was not found", r.URL.EscapedPath()))
|
||||
})
|
||||
}
|
||||
|
||||
// RoutableAPI represents an interface for things that can serve
|
||||
// as a provider of implementations for the swagger router
|
||||
type RoutableAPI interface {
|
||||
HandlerFor(string, string) (http.Handler, bool)
|
||||
ServeErrorFor(string) func(http.ResponseWriter, *http.Request, error)
|
||||
ConsumersFor([]string) map[string]runtime.Consumer
|
||||
ProducersFor([]string) map[string]runtime.Producer
|
||||
AuthenticatorsFor(map[string]spec.SecurityScheme) map[string]runtime.Authenticator
|
||||
Authorizer() runtime.Authorizer
|
||||
Formats() strfmt.Registry
|
||||
DefaultProduces() string
|
||||
DefaultConsumes() string
|
||||
}
|
||||
|
||||
// Router represents a swagger aware router
|
||||
type Router interface {
|
||||
Lookup(method, path string) (*MatchedRoute, bool)
|
||||
OtherMethods(method, path string) []string
|
||||
}
|
||||
|
||||
type defaultRouteBuilder struct {
|
||||
spec *loads.Document
|
||||
analyzer *analysis.Spec
|
||||
api RoutableAPI
|
||||
records map[string][]denco.Record
|
||||
}
|
||||
|
||||
type defaultRouter struct {
|
||||
spec *loads.Document
|
||||
routers map[string]*denco.Router
|
||||
}
|
||||
|
||||
func newDefaultRouteBuilder(spec *loads.Document, api RoutableAPI) *defaultRouteBuilder {
|
||||
return &defaultRouteBuilder{
|
||||
spec: spec,
|
||||
analyzer: analysis.New(spec.Spec()),
|
||||
api: api,
|
||||
records: make(map[string][]denco.Record),
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultRouter creates a default implemenation of the router
|
||||
func DefaultRouter(spec *loads.Document, api RoutableAPI) Router {
|
||||
builder := newDefaultRouteBuilder(spec, api)
|
||||
if spec != nil {
|
||||
for method, paths := range builder.analyzer.Operations() {
|
||||
for path, operation := range paths {
|
||||
fp := fpath.Join(spec.BasePath(), path)
|
||||
debugLog("adding route %s %s %q", method, fp, operation.ID)
|
||||
builder.AddRoute(method, fp, operation)
|
||||
}
|
||||
}
|
||||
}
|
||||
return builder.Build()
|
||||
}
|
||||
|
||||
// RouteAuthenticator is an authenticator that can compose several authenticators together.
|
||||
// It also knows when it contains an authenticator that allows for anonymous pass through.
|
||||
// Contains a group of 1 or more authenticators that have a logical AND relationship
|
||||
type RouteAuthenticator struct {
|
||||
Authenticator map[string]runtime.Authenticator
|
||||
Schemes []string
|
||||
Scopes map[string][]string
|
||||
allScopes []string
|
||||
commonScopes []string
|
||||
allowAnonymous bool
|
||||
}
|
||||
|
||||
func (ra *RouteAuthenticator) AllowsAnonymous() bool {
|
||||
return ra.allowAnonymous
|
||||
}
|
||||
|
||||
// AllScopes returns a list of unique scopes that is the combination
|
||||
// of all the scopes in the requirements
|
||||
func (ra *RouteAuthenticator) AllScopes() []string {
|
||||
return ra.allScopes
|
||||
}
|
||||
|
||||
// CommonScopes returns a list of unique scopes that are common in all the
|
||||
// scopes in the requirements
|
||||
func (ra *RouteAuthenticator) CommonScopes() []string {
|
||||
return ra.commonScopes
|
||||
}
|
||||
|
||||
// Authenticate Authenticator interface implementation
|
||||
func (ra *RouteAuthenticator) Authenticate(req *http.Request, route *MatchedRoute) (bool, interface{}, error) {
|
||||
if ra.allowAnonymous {
|
||||
route.Authenticator = ra
|
||||
return true, nil, nil
|
||||
}
|
||||
// iterate in proper order
|
||||
var lastResult interface{}
|
||||
for _, scheme := range ra.Schemes {
|
||||
if authenticator, ok := ra.Authenticator[scheme]; ok {
|
||||
applies, princ, err := authenticator.Authenticate(&security.ScopedAuthRequest{
|
||||
Request: req,
|
||||
RequiredScopes: ra.Scopes[scheme],
|
||||
})
|
||||
if !applies {
|
||||
return false, nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
route.Authenticator = ra
|
||||
return true, nil, err
|
||||
}
|
||||
lastResult = princ
|
||||
}
|
||||
}
|
||||
route.Authenticator = ra
|
||||
return true, lastResult, nil
|
||||
}
|
||||
|
||||
func stringSliceUnion(slices ...[]string) []string {
|
||||
unique := make(map[string]struct{})
|
||||
var result []string
|
||||
for _, slice := range slices {
|
||||
for _, entry := range slice {
|
||||
if _, ok := unique[entry]; ok {
|
||||
continue
|
||||
}
|
||||
unique[entry] = struct{}{}
|
||||
result = append(result, entry)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func stringSliceIntersection(slices ...[]string) []string {
|
||||
unique := make(map[string]int)
|
||||
var intersection []string
|
||||
|
||||
total := len(slices)
|
||||
var emptyCnt int
|
||||
for _, slice := range slices {
|
||||
if len(slice) == 0 {
|
||||
emptyCnt++
|
||||
continue
|
||||
}
|
||||
|
||||
for _, entry := range slice {
|
||||
unique[entry]++
|
||||
if unique[entry] == total-emptyCnt { // this entry appeared in all the non-empty slices
|
||||
intersection = append(intersection, entry)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return intersection
|
||||
}
|
||||
|
||||
// RouteAuthenticators represents a group of authenticators that represent a logical OR
|
||||
type RouteAuthenticators []RouteAuthenticator
|
||||
|
||||
// AllowsAnonymous returns true when there is an authenticator that means optional auth
|
||||
func (ras RouteAuthenticators) AllowsAnonymous() bool {
|
||||
for _, ra := range ras {
|
||||
if ra.AllowsAnonymous() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Authenticate method implemention so this collection can be used as authenticator
|
||||
func (ras RouteAuthenticators) Authenticate(req *http.Request, route *MatchedRoute) (bool, interface{}, error) {
|
||||
var lastError error
|
||||
var allowsAnon bool
|
||||
var anonAuth RouteAuthenticator
|
||||
|
||||
for _, ra := range ras {
|
||||
if ra.AllowsAnonymous() {
|
||||
anonAuth = ra
|
||||
allowsAnon = true
|
||||
continue
|
||||
}
|
||||
applies, usr, err := ra.Authenticate(req, route)
|
||||
if !applies || err != nil || usr == nil {
|
||||
if err != nil {
|
||||
lastError = err
|
||||
}
|
||||
continue
|
||||
}
|
||||
return applies, usr, nil
|
||||
}
|
||||
|
||||
if allowsAnon && lastError == nil {
|
||||
route.Authenticator = &anonAuth
|
||||
return true, nil, lastError
|
||||
}
|
||||
return lastError != nil, nil, lastError
|
||||
}
|
||||
|
||||
type routeEntry struct {
|
||||
PathPattern string
|
||||
BasePath string
|
||||
Operation *spec.Operation
|
||||
Consumes []string
|
||||
Consumers map[string]runtime.Consumer
|
||||
Produces []string
|
||||
Producers map[string]runtime.Producer
|
||||
Parameters map[string]spec.Parameter
|
||||
Handler http.Handler
|
||||
Formats strfmt.Registry
|
||||
Binder *untypedRequestBinder
|
||||
Authenticators RouteAuthenticators
|
||||
Authorizer runtime.Authorizer
|
||||
}
|
||||
|
||||
// MatchedRoute represents the route that was matched in this request
|
||||
type MatchedRoute struct {
|
||||
routeEntry
|
||||
Params RouteParams
|
||||
Consumer runtime.Consumer
|
||||
Producer runtime.Producer
|
||||
Authenticator *RouteAuthenticator
|
||||
}
|
||||
|
||||
// HasAuth returns true when the route has a security requirement defined
|
||||
func (m *MatchedRoute) HasAuth() bool {
|
||||
return len(m.Authenticators) > 0
|
||||
}
|
||||
|
||||
// NeedsAuth returns true when the request still
|
||||
// needs to perform authentication
|
||||
func (m *MatchedRoute) NeedsAuth() bool {
|
||||
return m.HasAuth() && m.Authenticator == nil
|
||||
}
|
||||
|
||||
func (d *defaultRouter) Lookup(method, path string) (*MatchedRoute, bool) {
|
||||
mth := strings.ToUpper(method)
|
||||
debugLog("looking up route for %s %s", method, path)
|
||||
if Debug {
|
||||
if len(d.routers) == 0 {
|
||||
debugLog("there are no known routers")
|
||||
}
|
||||
for meth := range d.routers {
|
||||
debugLog("got a router for %s", meth)
|
||||
}
|
||||
}
|
||||
if router, ok := d.routers[mth]; ok {
|
||||
if m, rp, ok := router.Lookup(fpath.Clean(path)); ok && m != nil {
|
||||
if entry, ok := m.(*routeEntry); ok {
|
||||
debugLog("found a route for %s %s with %d parameters", method, path, len(entry.Parameters))
|
||||
var params RouteParams
|
||||
for _, p := range rp {
|
||||
v, err := pathUnescape(p.Value)
|
||||
if err != nil {
|
||||
debugLog("failed to escape %q: %v", p.Value, err)
|
||||
v = p.Value
|
||||
}
|
||||
// a workaround to handle fragment/composing parameters until they are supported in denco router
|
||||
// check if this parameter is a fragment within a path segment
|
||||
if xpos := strings.Index(entry.PathPattern, fmt.Sprintf("{%s}", p.Name)) + len(p.Name) + 2; xpos < len(entry.PathPattern) && entry.PathPattern[xpos] != '/' {
|
||||
// extract fragment parameters
|
||||
ep := strings.Split(entry.PathPattern[xpos:], "/")[0]
|
||||
pnames, pvalues := decodeCompositParams(p.Name, v, ep, nil, nil)
|
||||
for i, pname := range pnames {
|
||||
params = append(params, RouteParam{Name: pname, Value: pvalues[i]})
|
||||
}
|
||||
} else {
|
||||
// use the parameter directly
|
||||
params = append(params, RouteParam{Name: p.Name, Value: v})
|
||||
}
|
||||
}
|
||||
return &MatchedRoute{routeEntry: *entry, Params: params}, true
|
||||
}
|
||||
} else {
|
||||
debugLog("couldn't find a route by path for %s %s", method, path)
|
||||
}
|
||||
} else {
|
||||
debugLog("couldn't find a route by method for %s %s", method, path)
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (d *defaultRouter) OtherMethods(method, path string) []string {
|
||||
mn := strings.ToUpper(method)
|
||||
var methods []string
|
||||
for k, v := range d.routers {
|
||||
if k != mn {
|
||||
if _, _, ok := v.Lookup(fpath.Clean(path)); ok {
|
||||
methods = append(methods, k)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
return methods
|
||||
}
|
||||
|
||||
// convert swagger parameters per path segment into a denco parameter as multiple parameters per segment are not supported in denco
|
||||
var pathConverter = regexp.MustCompile(`{(.+?)}([^/]*)`)
|
||||
|
||||
func decodeCompositParams(name string, value string, pattern string, names []string, values []string) ([]string, []string) {
|
||||
pleft := strings.Index(pattern, "{")
|
||||
names = append(names, name)
|
||||
if pleft < 0 {
|
||||
if strings.HasSuffix(value, pattern) {
|
||||
values = append(values, value[:len(value)-len(pattern)])
|
||||
} else {
|
||||
values = append(values, "")
|
||||
}
|
||||
} else {
|
||||
toskip := pattern[:pleft]
|
||||
pright := strings.Index(pattern, "}")
|
||||
vright := strings.Index(value, toskip)
|
||||
if vright >= 0 {
|
||||
values = append(values, value[:vright])
|
||||
} else {
|
||||
values = append(values, "")
|
||||
value = ""
|
||||
}
|
||||
return decodeCompositParams(pattern[pleft+1:pright], value[vright+len(toskip):], pattern[pright+1:], names, values)
|
||||
}
|
||||
return names, values
|
||||
}
|
||||
|
||||
func (d *defaultRouteBuilder) AddRoute(method, path string, operation *spec.Operation) {
|
||||
mn := strings.ToUpper(method)
|
||||
|
||||
bp := fpath.Clean(d.spec.BasePath())
|
||||
if len(bp) > 0 && bp[len(bp)-1] == '/' {
|
||||
bp = bp[:len(bp)-1]
|
||||
}
|
||||
|
||||
debugLog("operation: %#v", *operation)
|
||||
if handler, ok := d.api.HandlerFor(method, strings.TrimPrefix(path, bp)); ok {
|
||||
consumes := d.analyzer.ConsumesFor(operation)
|
||||
produces := d.analyzer.ProducesFor(operation)
|
||||
parameters := d.analyzer.ParamsFor(method, strings.TrimPrefix(path, bp))
|
||||
|
||||
record := denco.NewRecord(pathConverter.ReplaceAllString(path, ":$1"), &routeEntry{
|
||||
BasePath: bp,
|
||||
PathPattern: path,
|
||||
Operation: operation,
|
||||
Handler: handler,
|
||||
Consumes: consumes,
|
||||
Produces: produces,
|
||||
Consumers: d.api.ConsumersFor(normalizeOffers(consumes)),
|
||||
Producers: d.api.ProducersFor(normalizeOffers(produces)),
|
||||
Parameters: parameters,
|
||||
Formats: d.api.Formats(),
|
||||
Binder: newUntypedRequestBinder(parameters, d.spec.Spec(), d.api.Formats()),
|
||||
Authenticators: d.buildAuthenticators(operation),
|
||||
Authorizer: d.api.Authorizer(),
|
||||
})
|
||||
d.records[mn] = append(d.records[mn], record)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *defaultRouteBuilder) buildAuthenticators(operation *spec.Operation) RouteAuthenticators {
|
||||
requirements := d.analyzer.SecurityRequirementsFor(operation)
|
||||
var auths []RouteAuthenticator
|
||||
for _, reqs := range requirements {
|
||||
var schemes []string
|
||||
scopes := make(map[string][]string, len(reqs))
|
||||
var scopeSlices [][]string
|
||||
for _, req := range reqs {
|
||||
schemes = append(schemes, req.Name)
|
||||
scopes[req.Name] = req.Scopes
|
||||
scopeSlices = append(scopeSlices, req.Scopes)
|
||||
}
|
||||
|
||||
definitions := d.analyzer.SecurityDefinitionsForRequirements(reqs)
|
||||
authenticators := d.api.AuthenticatorsFor(definitions)
|
||||
auths = append(auths, RouteAuthenticator{
|
||||
Authenticator: authenticators,
|
||||
Schemes: schemes,
|
||||
Scopes: scopes,
|
||||
allScopes: stringSliceUnion(scopeSlices...),
|
||||
commonScopes: stringSliceIntersection(scopeSlices...),
|
||||
allowAnonymous: len(reqs) == 1 && reqs[0].Name == "",
|
||||
})
|
||||
}
|
||||
return auths
|
||||
}
|
||||
|
||||
func (d *defaultRouteBuilder) Build() *defaultRouter {
|
||||
routers := make(map[string]*denco.Router)
|
||||
for method, records := range d.records {
|
||||
router := denco.New()
|
||||
_ = router.Build(records)
|
||||
routers[method] = router
|
||||
}
|
||||
return &defaultRouter{
|
||||
spec: d.spec,
|
||||
routers: routers,
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue