podman
488 строк · 14.4 Кб
1// Copyright 2015 go-swagger maintainers
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package middleware
16
17import (
18"fmt"
19"net/http"
20fpath "path"
21"regexp"
22"strings"
23
24"github.com/go-openapi/runtime/security"
25"github.com/go-openapi/swag"
26
27"github.com/go-openapi/analysis"
28"github.com/go-openapi/errors"
29"github.com/go-openapi/loads"
30"github.com/go-openapi/spec"
31"github.com/go-openapi/strfmt"
32
33"github.com/go-openapi/runtime"
34"github.com/go-openapi/runtime/middleware/denco"
35)
36
37// RouteParam is a object to capture route params in a framework agnostic way.
38// implementations of the muxer should use these route params to communicate with the
39// swagger framework
40type RouteParam struct {
41Name string
42Value string
43}
44
45// RouteParams the collection of route params
46type RouteParams []RouteParam
47
48// Get gets the value for the route param for the specified key
49func (r RouteParams) Get(name string) string {
50vv, _, _ := r.GetOK(name)
51if len(vv) > 0 {
52return vv[len(vv)-1]
53}
54return ""
55}
56
57// GetOK gets the value but also returns booleans to indicate if a key or value
58// is present. This aids in validation and satisfies an interface in use there
59//
60// The returned values are: data, has key, has value
61func (r RouteParams) GetOK(name string) ([]string, bool, bool) {
62for _, p := range r {
63if p.Name == name {
64return []string{p.Value}, true, p.Value != ""
65}
66}
67return nil, false, false
68}
69
70// NewRouter creates a new context aware router middleware
71func NewRouter(ctx *Context, next http.Handler) http.Handler {
72if ctx.router == nil {
73ctx.router = DefaultRouter(ctx.spec, ctx.api)
74}
75
76return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
77if _, rCtx, ok := ctx.RouteInfo(r); ok {
78next.ServeHTTP(rw, rCtx)
79return
80}
81
82// Not found, check if it exists in the other methods first
83if others := ctx.AllowedMethods(r); len(others) > 0 {
84ctx.Respond(rw, r, ctx.analyzer.RequiredProduces(), nil, errors.MethodNotAllowed(r.Method, others))
85return
86}
87
88ctx.Respond(rw, r, ctx.analyzer.RequiredProduces(), nil, errors.NotFound("path %s was not found", r.URL.EscapedPath()))
89})
90}
91
92// RoutableAPI represents an interface for things that can serve
93// as a provider of implementations for the swagger router
94type RoutableAPI interface {
95HandlerFor(string, string) (http.Handler, bool)
96ServeErrorFor(string) func(http.ResponseWriter, *http.Request, error)
97ConsumersFor([]string) map[string]runtime.Consumer
98ProducersFor([]string) map[string]runtime.Producer
99AuthenticatorsFor(map[string]spec.SecurityScheme) map[string]runtime.Authenticator
100Authorizer() runtime.Authorizer
101Formats() strfmt.Registry
102DefaultProduces() string
103DefaultConsumes() string
104}
105
106// Router represents a swagger aware router
107type Router interface {
108Lookup(method, path string) (*MatchedRoute, bool)
109OtherMethods(method, path string) []string
110}
111
112type defaultRouteBuilder struct {
113spec *loads.Document
114analyzer *analysis.Spec
115api RoutableAPI
116records map[string][]denco.Record
117}
118
119type defaultRouter struct {
120spec *loads.Document
121routers map[string]*denco.Router
122}
123
124func newDefaultRouteBuilder(spec *loads.Document, api RoutableAPI) *defaultRouteBuilder {
125return &defaultRouteBuilder{
126spec: spec,
127analyzer: analysis.New(spec.Spec()),
128api: api,
129records: make(map[string][]denco.Record),
130}
131}
132
133// DefaultRouter creates a default implemenation of the router
134func DefaultRouter(spec *loads.Document, api RoutableAPI) Router {
135builder := newDefaultRouteBuilder(spec, api)
136if spec != nil {
137for method, paths := range builder.analyzer.Operations() {
138for path, operation := range paths {
139fp := fpath.Join(spec.BasePath(), path)
140debugLog("adding route %s %s %q", method, fp, operation.ID)
141builder.AddRoute(method, fp, operation)
142}
143}
144}
145return builder.Build()
146}
147
148// RouteAuthenticator is an authenticator that can compose several authenticators together.
149// It also knows when it contains an authenticator that allows for anonymous pass through.
150// Contains a group of 1 or more authenticators that have a logical AND relationship
151type RouteAuthenticator struct {
152Authenticator map[string]runtime.Authenticator
153Schemes []string
154Scopes map[string][]string
155allScopes []string
156commonScopes []string
157allowAnonymous bool
158}
159
160func (ra *RouteAuthenticator) AllowsAnonymous() bool {
161return ra.allowAnonymous
162}
163
164// AllScopes returns a list of unique scopes that is the combination
165// of all the scopes in the requirements
166func (ra *RouteAuthenticator) AllScopes() []string {
167return ra.allScopes
168}
169
170// CommonScopes returns a list of unique scopes that are common in all the
171// scopes in the requirements
172func (ra *RouteAuthenticator) CommonScopes() []string {
173return ra.commonScopes
174}
175
176// Authenticate Authenticator interface implementation
177func (ra *RouteAuthenticator) Authenticate(req *http.Request, route *MatchedRoute) (bool, interface{}, error) {
178if ra.allowAnonymous {
179route.Authenticator = ra
180return true, nil, nil
181}
182// iterate in proper order
183var lastResult interface{}
184for _, scheme := range ra.Schemes {
185if authenticator, ok := ra.Authenticator[scheme]; ok {
186applies, princ, err := authenticator.Authenticate(&security.ScopedAuthRequest{
187Request: req,
188RequiredScopes: ra.Scopes[scheme],
189})
190if !applies {
191return false, nil, nil
192}
193if err != nil {
194route.Authenticator = ra
195return true, nil, err
196}
197lastResult = princ
198}
199}
200route.Authenticator = ra
201return true, lastResult, nil
202}
203
204func stringSliceUnion(slices ...[]string) []string {
205unique := make(map[string]struct{})
206var result []string
207for _, slice := range slices {
208for _, entry := range slice {
209if _, ok := unique[entry]; ok {
210continue
211}
212unique[entry] = struct{}{}
213result = append(result, entry)
214}
215}
216return result
217}
218
219func stringSliceIntersection(slices ...[]string) []string {
220unique := make(map[string]int)
221var intersection []string
222
223total := len(slices)
224var emptyCnt int
225for _, slice := range slices {
226if len(slice) == 0 {
227emptyCnt++
228continue
229}
230
231for _, entry := range slice {
232unique[entry]++
233if unique[entry] == total-emptyCnt { // this entry appeared in all the non-empty slices
234intersection = append(intersection, entry)
235}
236}
237}
238
239return intersection
240}
241
242// RouteAuthenticators represents a group of authenticators that represent a logical OR
243type RouteAuthenticators []RouteAuthenticator
244
245// AllowsAnonymous returns true when there is an authenticator that means optional auth
246func (ras RouteAuthenticators) AllowsAnonymous() bool {
247for _, ra := range ras {
248if ra.AllowsAnonymous() {
249return true
250}
251}
252return false
253}
254
255// Authenticate method implemention so this collection can be used as authenticator
256func (ras RouteAuthenticators) Authenticate(req *http.Request, route *MatchedRoute) (bool, interface{}, error) {
257var lastError error
258var allowsAnon bool
259var anonAuth RouteAuthenticator
260
261for _, ra := range ras {
262if ra.AllowsAnonymous() {
263anonAuth = ra
264allowsAnon = true
265continue
266}
267applies, usr, err := ra.Authenticate(req, route)
268if !applies || err != nil || usr == nil {
269if err != nil {
270lastError = err
271}
272continue
273}
274return applies, usr, nil
275}
276
277if allowsAnon && lastError == nil {
278route.Authenticator = &anonAuth
279return true, nil, lastError
280}
281return lastError != nil, nil, lastError
282}
283
284type routeEntry struct {
285PathPattern string
286BasePath string
287Operation *spec.Operation
288Consumes []string
289Consumers map[string]runtime.Consumer
290Produces []string
291Producers map[string]runtime.Producer
292Parameters map[string]spec.Parameter
293Handler http.Handler
294Formats strfmt.Registry
295Binder *UntypedRequestBinder
296Authenticators RouteAuthenticators
297Authorizer runtime.Authorizer
298}
299
300// MatchedRoute represents the route that was matched in this request
301type MatchedRoute struct {
302routeEntry
303Params RouteParams
304Consumer runtime.Consumer
305Producer runtime.Producer
306Authenticator *RouteAuthenticator
307}
308
309// HasAuth returns true when the route has a security requirement defined
310func (m *MatchedRoute) HasAuth() bool {
311return len(m.Authenticators) > 0
312}
313
314// NeedsAuth returns true when the request still
315// needs to perform authentication
316func (m *MatchedRoute) NeedsAuth() bool {
317return m.HasAuth() && m.Authenticator == nil
318}
319
320func (d *defaultRouter) Lookup(method, path string) (*MatchedRoute, bool) {
321mth := strings.ToUpper(method)
322debugLog("looking up route for %s %s", method, path)
323if Debug {
324if len(d.routers) == 0 {
325debugLog("there are no known routers")
326}
327for meth := range d.routers {
328debugLog("got a router for %s", meth)
329}
330}
331if router, ok := d.routers[mth]; ok {
332if m, rp, ok := router.Lookup(fpath.Clean(path)); ok && m != nil {
333if entry, ok := m.(*routeEntry); ok {
334debugLog("found a route for %s %s with %d parameters", method, path, len(entry.Parameters))
335var params RouteParams
336for _, p := range rp {
337v, err := pathUnescape(p.Value)
338if err != nil {
339debugLog("failed to escape %q: %v", p.Value, err)
340v = p.Value
341}
342// a workaround to handle fragment/composing parameters until they are supported in denco router
343// check if this parameter is a fragment within a path segment
344if xpos := strings.Index(entry.PathPattern, fmt.Sprintf("{%s}", p.Name)) + len(p.Name) + 2; xpos < len(entry.PathPattern) && entry.PathPattern[xpos] != '/' {
345// extract fragment parameters
346ep := strings.Split(entry.PathPattern[xpos:], "/")[0]
347pnames, pvalues := decodeCompositParams(p.Name, v, ep, nil, nil)
348for i, pname := range pnames {
349params = append(params, RouteParam{Name: pname, Value: pvalues[i]})
350}
351} else {
352// use the parameter directly
353params = append(params, RouteParam{Name: p.Name, Value: v})
354}
355}
356return &MatchedRoute{routeEntry: *entry, Params: params}, true
357}
358} else {
359debugLog("couldn't find a route by path for %s %s", method, path)
360}
361} else {
362debugLog("couldn't find a route by method for %s %s", method, path)
363}
364return nil, false
365}
366
367func (d *defaultRouter) OtherMethods(method, path string) []string {
368mn := strings.ToUpper(method)
369var methods []string
370for k, v := range d.routers {
371if k != mn {
372if _, _, ok := v.Lookup(fpath.Clean(path)); ok {
373methods = append(methods, k)
374continue
375}
376}
377}
378return methods
379}
380
381// convert swagger parameters per path segment into a denco parameter as multiple parameters per segment are not supported in denco
382var pathConverter = regexp.MustCompile(`{(.+?)}([^/]*)`)
383
384func decodeCompositParams(name string, value string, pattern string, names []string, values []string) ([]string, []string) {
385pleft := strings.Index(pattern, "{")
386names = append(names, name)
387if pleft < 0 {
388if strings.HasSuffix(value, pattern) {
389values = append(values, value[:len(value)-len(pattern)])
390} else {
391values = append(values, "")
392}
393} else {
394toskip := pattern[:pleft]
395pright := strings.Index(pattern, "}")
396vright := strings.Index(value, toskip)
397if vright >= 0 {
398values = append(values, value[:vright])
399} else {
400values = append(values, "")
401value = ""
402}
403return decodeCompositParams(pattern[pleft+1:pright], value[vright+len(toskip):], pattern[pright+1:], names, values)
404}
405return names, values
406}
407
408func (d *defaultRouteBuilder) AddRoute(method, path string, operation *spec.Operation) {
409mn := strings.ToUpper(method)
410
411bp := fpath.Clean(d.spec.BasePath())
412if len(bp) > 0 && bp[len(bp)-1] == '/' {
413bp = bp[:len(bp)-1]
414}
415
416debugLog("operation: %#v", *operation)
417if handler, ok := d.api.HandlerFor(method, strings.TrimPrefix(path, bp)); ok {
418consumes := d.analyzer.ConsumesFor(operation)
419produces := d.analyzer.ProducesFor(operation)
420parameters := d.analyzer.ParamsFor(method, strings.TrimPrefix(path, bp))
421
422// add API defaults if not part of the spec
423if defConsumes := d.api.DefaultConsumes(); defConsumes != "" && !swag.ContainsStringsCI(consumes, defConsumes) {
424consumes = append(consumes, defConsumes)
425}
426
427if defProduces := d.api.DefaultProduces(); defProduces != "" && !swag.ContainsStringsCI(produces, defProduces) {
428produces = append(produces, defProduces)
429}
430
431record := denco.NewRecord(pathConverter.ReplaceAllString(path, ":$1"), &routeEntry{
432BasePath: bp,
433PathPattern: path,
434Operation: operation,
435Handler: handler,
436Consumes: consumes,
437Produces: produces,
438Consumers: d.api.ConsumersFor(normalizeOffers(consumes)),
439Producers: d.api.ProducersFor(normalizeOffers(produces)),
440Parameters: parameters,
441Formats: d.api.Formats(),
442Binder: NewUntypedRequestBinder(parameters, d.spec.Spec(), d.api.Formats()),
443Authenticators: d.buildAuthenticators(operation),
444Authorizer: d.api.Authorizer(),
445})
446d.records[mn] = append(d.records[mn], record)
447}
448}
449
450func (d *defaultRouteBuilder) buildAuthenticators(operation *spec.Operation) RouteAuthenticators {
451requirements := d.analyzer.SecurityRequirementsFor(operation)
452var auths []RouteAuthenticator
453for _, reqs := range requirements {
454var schemes []string
455scopes := make(map[string][]string, len(reqs))
456var scopeSlices [][]string
457for _, req := range reqs {
458schemes = append(schemes, req.Name)
459scopes[req.Name] = req.Scopes
460scopeSlices = append(scopeSlices, req.Scopes)
461}
462
463definitions := d.analyzer.SecurityDefinitionsForRequirements(reqs)
464authenticators := d.api.AuthenticatorsFor(definitions)
465auths = append(auths, RouteAuthenticator{
466Authenticator: authenticators,
467Schemes: schemes,
468Scopes: scopes,
469allScopes: stringSliceUnion(scopeSlices...),
470commonScopes: stringSliceIntersection(scopeSlices...),
471allowAnonymous: len(reqs) == 1 && reqs[0].Name == "",
472})
473}
474return auths
475}
476
477func (d *defaultRouteBuilder) Build() *defaultRouter {
478routers := make(map[string]*denco.Router)
479for method, records := range d.records {
480router := denco.New()
481_ = router.Build(records)
482routers[method] = router
483}
484return &defaultRouter{
485spec: d.spec,
486routers: routers,
487}
488}
489