podman

Форк
0
635 строк · 19.6 Кб
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

15
package middleware
16

17
import (
18
	stdContext "context"
19
	"fmt"
20
	"net/http"
21
	"strings"
22
	"sync"
23

24
	"github.com/go-openapi/analysis"
25
	"github.com/go-openapi/errors"
26
	"github.com/go-openapi/loads"
27
	"github.com/go-openapi/spec"
28
	"github.com/go-openapi/strfmt"
29

30
	"github.com/go-openapi/runtime"
31
	"github.com/go-openapi/runtime/logger"
32
	"github.com/go-openapi/runtime/middleware/untyped"
33
	"github.com/go-openapi/runtime/security"
34
)
35

36
// Debug when true turns on verbose logging
37
var Debug = logger.DebugEnabled()
38
var Logger logger.Logger = logger.StandardLogger{}
39

40
func debugLog(format string, args ...interface{}) {
41
	if Debug {
42
		Logger.Printf(format, args...)
43
	}
44
}
45

46
// A Builder can create middlewares
47
type Builder func(http.Handler) http.Handler
48

49
// PassthroughBuilder returns the handler, aka the builder identity function
50
func PassthroughBuilder(handler http.Handler) http.Handler { return handler }
51

52
// RequestBinder is an interface for types to implement
53
// when they want to be able to bind from a request
54
type RequestBinder interface {
55
	BindRequest(*http.Request, *MatchedRoute) error
56
}
57

58
// Responder is an interface for types to implement
59
// when they want to be considered for writing HTTP responses
60
type Responder interface {
61
	WriteResponse(http.ResponseWriter, runtime.Producer)
62
}
63

64
// ResponderFunc wraps a func as a Responder interface
65
type ResponderFunc func(http.ResponseWriter, runtime.Producer)
66

67
// WriteResponse writes to the response
68
func (fn ResponderFunc) WriteResponse(rw http.ResponseWriter, pr runtime.Producer) {
69
	fn(rw, pr)
70
}
71

72
// Context is a type safe wrapper around an untyped request context
73
// used throughout to store request context with the standard context attached
74
// to the http.Request
75
type Context struct {
76
	spec     *loads.Document
77
	analyzer *analysis.Spec
78
	api      RoutableAPI
79
	router   Router
80
}
81

82
type routableUntypedAPI struct {
83
	api             *untyped.API
84
	hlock           *sync.Mutex
85
	handlers        map[string]map[string]http.Handler
86
	defaultConsumes string
87
	defaultProduces string
88
}
89

90
func newRoutableUntypedAPI(spec *loads.Document, api *untyped.API, context *Context) *routableUntypedAPI {
91
	var handlers map[string]map[string]http.Handler
92
	if spec == nil || api == nil {
93
		return nil
94
	}
95
	analyzer := analysis.New(spec.Spec())
96
	for method, hls := range analyzer.Operations() {
97
		um := strings.ToUpper(method)
98
		for path, op := range hls {
99
			schemes := analyzer.SecurityRequirementsFor(op)
100

101
			if oh, ok := api.OperationHandlerFor(method, path); ok {
102
				if handlers == nil {
103
					handlers = make(map[string]map[string]http.Handler)
104
				}
105
				if b, ok := handlers[um]; !ok || b == nil {
106
					handlers[um] = make(map[string]http.Handler)
107
				}
108

109
				var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
110
					// lookup route info in the context
111
					route, rCtx, _ := context.RouteInfo(r)
112
					if rCtx != nil {
113
						r = rCtx
114
					}
115

116
					// bind and validate the request using reflection
117
					var bound interface{}
118
					var validation error
119
					bound, r, validation = context.BindAndValidate(r, route)
120
					if validation != nil {
121
						context.Respond(w, r, route.Produces, route, validation)
122
						return
123
					}
124

125
					// actually handle the request
126
					result, err := oh.Handle(bound)
127
					if err != nil {
128
						// respond with failure
129
						context.Respond(w, r, route.Produces, route, err)
130
						return
131
					}
132

133
					// respond with success
134
					context.Respond(w, r, route.Produces, route, result)
135
				})
136

137
				if len(schemes) > 0 {
138
					handler = newSecureAPI(context, handler)
139
				}
140
				handlers[um][path] = handler
141
			}
142
		}
143
	}
144

145
	return &routableUntypedAPI{
146
		api:             api,
147
		hlock:           new(sync.Mutex),
148
		handlers:        handlers,
149
		defaultProduces: api.DefaultProduces,
150
		defaultConsumes: api.DefaultConsumes,
151
	}
152
}
153

154
func (r *routableUntypedAPI) HandlerFor(method, path string) (http.Handler, bool) {
155
	r.hlock.Lock()
156
	paths, ok := r.handlers[strings.ToUpper(method)]
157
	if !ok {
158
		r.hlock.Unlock()
159
		return nil, false
160
	}
161
	handler, ok := paths[path]
162
	r.hlock.Unlock()
163
	return handler, ok
164
}
165
func (r *routableUntypedAPI) ServeErrorFor(operationID string) func(http.ResponseWriter, *http.Request, error) {
166
	return r.api.ServeError
167
}
168
func (r *routableUntypedAPI) ConsumersFor(mediaTypes []string) map[string]runtime.Consumer {
169
	return r.api.ConsumersFor(mediaTypes)
170
}
171
func (r *routableUntypedAPI) ProducersFor(mediaTypes []string) map[string]runtime.Producer {
172
	return r.api.ProducersFor(mediaTypes)
173
}
174
func (r *routableUntypedAPI) AuthenticatorsFor(schemes map[string]spec.SecurityScheme) map[string]runtime.Authenticator {
175
	return r.api.AuthenticatorsFor(schemes)
176
}
177
func (r *routableUntypedAPI) Authorizer() runtime.Authorizer {
178
	return r.api.Authorizer()
179
}
180
func (r *routableUntypedAPI) Formats() strfmt.Registry {
181
	return r.api.Formats()
182
}
183

184
func (r *routableUntypedAPI) DefaultProduces() string {
185
	return r.defaultProduces
186
}
187

188
func (r *routableUntypedAPI) DefaultConsumes() string {
189
	return r.defaultConsumes
190
}
191

192
// NewRoutableContext creates a new context for a routable API
193
func NewRoutableContext(spec *loads.Document, routableAPI RoutableAPI, routes Router) *Context {
194
	var an *analysis.Spec
195
	if spec != nil {
196
		an = analysis.New(spec.Spec())
197
	}
198

199
	return NewRoutableContextWithAnalyzedSpec(spec, an, routableAPI, routes)
200
}
201

202
// NewRoutableContextWithAnalyzedSpec is like NewRoutableContext but takes in input the analysed spec too
203
func NewRoutableContextWithAnalyzedSpec(spec *loads.Document, an *analysis.Spec, routableAPI RoutableAPI, routes Router) *Context {
204
	// Either there are no spec doc and analysis, or both of them.
205
	if !((spec == nil && an == nil) || (spec != nil && an != nil)) {
206
		panic(errors.New(http.StatusInternalServerError, "routable context requires either both spec doc and analysis, or none of them"))
207
	}
208

209
	ctx := &Context{spec: spec, api: routableAPI, analyzer: an, router: routes}
210
	return ctx
211
}
212

213
// NewContext creates a new context wrapper
214
func NewContext(spec *loads.Document, api *untyped.API, routes Router) *Context {
215
	var an *analysis.Spec
216
	if spec != nil {
217
		an = analysis.New(spec.Spec())
218
	}
219
	ctx := &Context{spec: spec, analyzer: an}
220
	ctx.api = newRoutableUntypedAPI(spec, api, ctx)
221
	ctx.router = routes
222
	return ctx
223
}
224

225
// Serve serves the specified spec with the specified api registrations as a http.Handler
226
func Serve(spec *loads.Document, api *untyped.API) http.Handler {
227
	return ServeWithBuilder(spec, api, PassthroughBuilder)
228
}
229

230
// ServeWithBuilder serves the specified spec with the specified api registrations as a http.Handler that is decorated
231
// by the Builder
232
func ServeWithBuilder(spec *loads.Document, api *untyped.API, builder Builder) http.Handler {
233
	context := NewContext(spec, api, nil)
234
	return context.APIHandler(builder)
235
}
236

237
type contextKey int8
238

239
const (
240
	_ contextKey = iota
241
	ctxContentType
242
	ctxResponseFormat
243
	ctxMatchedRoute
244
	ctxBoundParams
245
	ctxSecurityPrincipal
246
	ctxSecurityScopes
247
)
248

249
// MatchedRouteFrom request context value.
250
func MatchedRouteFrom(req *http.Request) *MatchedRoute {
251
	mr := req.Context().Value(ctxMatchedRoute)
252
	if mr == nil {
253
		return nil
254
	}
255
	if res, ok := mr.(*MatchedRoute); ok {
256
		return res
257
	}
258
	return nil
259
}
260

261
// SecurityPrincipalFrom request context value.
262
func SecurityPrincipalFrom(req *http.Request) interface{} {
263
	return req.Context().Value(ctxSecurityPrincipal)
264
}
265

266
// SecurityScopesFrom request context value.
267
func SecurityScopesFrom(req *http.Request) []string {
268
	rs := req.Context().Value(ctxSecurityScopes)
269
	if res, ok := rs.([]string); ok {
270
		return res
271
	}
272
	return nil
273
}
274

275
type contentTypeValue struct {
276
	MediaType string
277
	Charset   string
278
}
279

280
// BasePath returns the base path for this API
281
func (c *Context) BasePath() string {
282
	return c.spec.BasePath()
283
}
284

285
// RequiredProduces returns the accepted content types for responses
286
func (c *Context) RequiredProduces() []string {
287
	return c.analyzer.RequiredProduces()
288
}
289

290
// BindValidRequest binds a params object to a request but only when the request is valid
291
// if the request is not valid an error will be returned
292
func (c *Context) BindValidRequest(request *http.Request, route *MatchedRoute, binder RequestBinder) error {
293
	var res []error
294
	var requestContentType string
295

296
	// check and validate content type, select consumer
297
	if runtime.HasBody(request) {
298
		ct, _, err := runtime.ContentType(request.Header)
299
		if err != nil {
300
			res = append(res, err)
301
		} else {
302
			if err := validateContentType(route.Consumes, ct); err != nil {
303
				res = append(res, err)
304
			}
305
			if len(res) == 0 {
306
				cons, ok := route.Consumers[ct]
307
				if !ok {
308
					res = append(res, errors.New(500, "no consumer registered for %s", ct))
309
				} else {
310
					route.Consumer = cons
311
					requestContentType = ct
312
				}
313
			}
314
		}
315
	}
316

317
	// check and validate the response format
318
	if len(res) == 0 {
319
		// if the route does not provide Produces and a default contentType could not be identified
320
		// based on a body, typical for GET and DELETE requests, then default contentType to.
321
		if len(route.Produces) == 0 && requestContentType == "" {
322
			requestContentType = "*/*"
323
		}
324

325
		if str := NegotiateContentType(request, route.Produces, requestContentType); str == "" {
326
			res = append(res, errors.InvalidResponseFormat(request.Header.Get(runtime.HeaderAccept), route.Produces))
327
		}
328
	}
329

330
	// now bind the request with the provided binder
331
	// it's assumed the binder will also validate the request and return an error if the
332
	// request is invalid
333
	if binder != nil && len(res) == 0 {
334
		if err := binder.BindRequest(request, route); err != nil {
335
			return err
336
		}
337
	}
338

339
	if len(res) > 0 {
340
		return errors.CompositeValidationError(res...)
341
	}
342
	return nil
343
}
344

345
// ContentType gets the parsed value of a content type
346
// Returns the media type, its charset and a shallow copy of the request
347
// when its context doesn't contain the content type value, otherwise it returns
348
// the same request
349
// Returns the error that runtime.ContentType may retunrs.
350
func (c *Context) ContentType(request *http.Request) (string, string, *http.Request, error) {
351
	var rCtx = request.Context()
352

353
	if v, ok := rCtx.Value(ctxContentType).(*contentTypeValue); ok {
354
		return v.MediaType, v.Charset, request, nil
355
	}
356

357
	mt, cs, err := runtime.ContentType(request.Header)
358
	if err != nil {
359
		return "", "", nil, err
360
	}
361
	rCtx = stdContext.WithValue(rCtx, ctxContentType, &contentTypeValue{mt, cs})
362
	return mt, cs, request.WithContext(rCtx), nil
363
}
364

365
// LookupRoute looks a route up and returns true when it is found
366
func (c *Context) LookupRoute(request *http.Request) (*MatchedRoute, bool) {
367
	if route, ok := c.router.Lookup(request.Method, request.URL.EscapedPath()); ok {
368
		return route, ok
369
	}
370
	return nil, false
371
}
372

373
// RouteInfo tries to match a route for this request
374
// Returns the matched route, a shallow copy of the request if its context
375
// contains the matched router, otherwise the same request, and a bool to
376
// indicate if it the request matches one of the routes, if it doesn't
377
// then it returns false and nil for the other two return values
378
func (c *Context) RouteInfo(request *http.Request) (*MatchedRoute, *http.Request, bool) {
379
	var rCtx = request.Context()
380

381
	if v, ok := rCtx.Value(ctxMatchedRoute).(*MatchedRoute); ok {
382
		return v, request, ok
383
	}
384

385
	if route, ok := c.LookupRoute(request); ok {
386
		rCtx = stdContext.WithValue(rCtx, ctxMatchedRoute, route)
387
		return route, request.WithContext(rCtx), ok
388
	}
389

390
	return nil, nil, false
391
}
392

393
// ResponseFormat negotiates the response content type
394
// Returns the response format and a shallow copy of the request if its context
395
// doesn't contain the response format, otherwise the same request
396
func (c *Context) ResponseFormat(r *http.Request, offers []string) (string, *http.Request) {
397
	var rCtx = r.Context()
398

399
	if v, ok := rCtx.Value(ctxResponseFormat).(string); ok {
400
		debugLog("[%s %s] found response format %q in context", r.Method, r.URL.Path, v)
401
		return v, r
402
	}
403

404
	format := NegotiateContentType(r, offers, "")
405
	if format != "" {
406
		debugLog("[%s %s] set response format %q in context", r.Method, r.URL.Path, format)
407
		r = r.WithContext(stdContext.WithValue(rCtx, ctxResponseFormat, format))
408
	}
409
	debugLog("[%s %s] negotiated response format %q", r.Method, r.URL.Path, format)
410
	return format, r
411
}
412

413
// AllowedMethods gets the allowed methods for the path of this request
414
func (c *Context) AllowedMethods(request *http.Request) []string {
415
	return c.router.OtherMethods(request.Method, request.URL.EscapedPath())
416
}
417

418
// ResetAuth removes the current principal from the request context
419
func (c *Context) ResetAuth(request *http.Request) *http.Request {
420
	rctx := request.Context()
421
	rctx = stdContext.WithValue(rctx, ctxSecurityPrincipal, nil)
422
	rctx = stdContext.WithValue(rctx, ctxSecurityScopes, nil)
423
	return request.WithContext(rctx)
424
}
425

426
// Authorize authorizes the request
427
// Returns the principal object and a shallow copy of the request when its
428
// context doesn't contain the principal, otherwise the same request or an error
429
// (the last) if one of the authenticators returns one or an Unauthenticated error
430
func (c *Context) Authorize(request *http.Request, route *MatchedRoute) (interface{}, *http.Request, error) {
431
	if route == nil || !route.HasAuth() {
432
		return nil, nil, nil
433
	}
434

435
	var rCtx = request.Context()
436
	if v := rCtx.Value(ctxSecurityPrincipal); v != nil {
437
		return v, request, nil
438
	}
439

440
	applies, usr, err := route.Authenticators.Authenticate(request, route)
441
	if !applies || err != nil || !route.Authenticators.AllowsAnonymous() && usr == nil {
442
		if err != nil {
443
			return nil, nil, err
444
		}
445
		return nil, nil, errors.Unauthenticated("invalid credentials")
446
	}
447
	if route.Authorizer != nil {
448
		if err := route.Authorizer.Authorize(request, usr); err != nil {
449
			if _, ok := err.(errors.Error); ok {
450
				return nil, nil, err
451
			}
452

453
			return nil, nil, errors.New(http.StatusForbidden, err.Error())
454
		}
455
	}
456

457
	rCtx = request.Context()
458

459
	rCtx = stdContext.WithValue(rCtx, ctxSecurityPrincipal, usr)
460
	rCtx = stdContext.WithValue(rCtx, ctxSecurityScopes, route.Authenticator.AllScopes())
461
	return usr, request.WithContext(rCtx), nil
462
}
463

464
// BindAndValidate binds and validates the request
465
// Returns the validation map and a shallow copy of the request when its context
466
// doesn't contain the validation, otherwise it returns the same request or an
467
// CompositeValidationError error
468
func (c *Context) BindAndValidate(request *http.Request, matched *MatchedRoute) (interface{}, *http.Request, error) {
469
	var rCtx = request.Context()
470

471
	if v, ok := rCtx.Value(ctxBoundParams).(*validation); ok {
472
		debugLog("got cached validation (valid: %t)", len(v.result) == 0)
473
		if len(v.result) > 0 {
474
			return v.bound, request, errors.CompositeValidationError(v.result...)
475
		}
476
		return v.bound, request, nil
477
	}
478
	result := validateRequest(c, request, matched)
479
	rCtx = stdContext.WithValue(rCtx, ctxBoundParams, result)
480
	request = request.WithContext(rCtx)
481
	if len(result.result) > 0 {
482
		return result.bound, request, errors.CompositeValidationError(result.result...)
483
	}
484
	debugLog("no validation errors found")
485
	return result.bound, request, nil
486
}
487

488
// NotFound the default not found responder for when no route has been matched yet
489
func (c *Context) NotFound(rw http.ResponseWriter, r *http.Request) {
490
	c.Respond(rw, r, []string{c.api.DefaultProduces()}, nil, errors.NotFound("not found"))
491
}
492

493
// Respond renders the response after doing some content negotiation
494
func (c *Context) Respond(rw http.ResponseWriter, r *http.Request, produces []string, route *MatchedRoute, data interface{}) {
495
	debugLog("responding to %s %s with produces: %v", r.Method, r.URL.Path, produces)
496
	offers := []string{}
497
	for _, mt := range produces {
498
		if mt != c.api.DefaultProduces() {
499
			offers = append(offers, mt)
500
		}
501
	}
502
	// the default producer is last so more specific producers take precedence
503
	offers = append(offers, c.api.DefaultProduces())
504
	debugLog("offers: %v", offers)
505

506
	var format string
507
	format, r = c.ResponseFormat(r, offers)
508
	rw.Header().Set(runtime.HeaderContentType, format)
509

510
	if resp, ok := data.(Responder); ok {
511
		producers := route.Producers
512
		// producers contains keys with normalized format, if a format has MIME type parameter such as `text/plain; charset=utf-8`
513
		// then you must provide `text/plain` to get the correct producer. HOWEVER, format here is not normalized.
514
		prod, ok := producers[normalizeOffer(format)]
515
		if !ok {
516
			prods := c.api.ProducersFor(normalizeOffers([]string{c.api.DefaultProduces()}))
517
			pr, ok := prods[c.api.DefaultProduces()]
518
			if !ok {
519
				panic(errors.New(http.StatusInternalServerError, "can't find a producer for "+format))
520
			}
521
			prod = pr
522
		}
523
		resp.WriteResponse(rw, prod)
524
		return
525
	}
526

527
	if err, ok := data.(error); ok {
528
		if format == "" {
529
			rw.Header().Set(runtime.HeaderContentType, runtime.JSONMime)
530
		}
531

532
		if realm := security.FailedBasicAuth(r); realm != "" {
533
			rw.Header().Set("WWW-Authenticate", fmt.Sprintf("Basic realm=%q", realm))
534
		}
535

536
		if route == nil || route.Operation == nil {
537
			c.api.ServeErrorFor("")(rw, r, err)
538
			return
539
		}
540
		c.api.ServeErrorFor(route.Operation.ID)(rw, r, err)
541
		return
542
	}
543

544
	if route == nil || route.Operation == nil {
545
		rw.WriteHeader(200)
546
		if r.Method == "HEAD" {
547
			return
548
		}
549
		producers := c.api.ProducersFor(normalizeOffers(offers))
550
		prod, ok := producers[format]
551
		if !ok {
552
			panic(errors.New(http.StatusInternalServerError, "can't find a producer for "+format))
553
		}
554
		if err := prod.Produce(rw, data); err != nil {
555
			panic(err) // let the recovery middleware deal with this
556
		}
557
		return
558
	}
559

560
	if _, code, ok := route.Operation.SuccessResponse(); ok {
561
		rw.WriteHeader(code)
562
		if code == 204 || r.Method == "HEAD" {
563
			return
564
		}
565

566
		producers := route.Producers
567
		prod, ok := producers[format]
568
		if !ok {
569
			if !ok {
570
				prods := c.api.ProducersFor(normalizeOffers([]string{c.api.DefaultProduces()}))
571
				pr, ok := prods[c.api.DefaultProduces()]
572
				if !ok {
573
					panic(errors.New(http.StatusInternalServerError, "can't find a producer for "+format))
574
				}
575
				prod = pr
576
			}
577
		}
578
		if err := prod.Produce(rw, data); err != nil {
579
			panic(err) // let the recovery middleware deal with this
580
		}
581
		return
582
	}
583

584
	c.api.ServeErrorFor(route.Operation.ID)(rw, r, errors.New(http.StatusInternalServerError, "can't produce response"))
585
}
586

587
func (c *Context) APIHandlerSwaggerUI(builder Builder) http.Handler {
588
	b := builder
589
	if b == nil {
590
		b = PassthroughBuilder
591
	}
592

593
	var title string
594
	sp := c.spec.Spec()
595
	if sp != nil && sp.Info != nil && sp.Info.Title != "" {
596
		title = sp.Info.Title
597
	}
598

599
	swaggerUIOpts := SwaggerUIOpts{
600
		BasePath: c.BasePath(),
601
		Title:    title,
602
	}
603

604
	return Spec("", c.spec.Raw(), SwaggerUI(swaggerUIOpts, c.RoutesHandler(b)))
605
}
606

607
// APIHandler returns a handler to serve the API, this includes a swagger spec, router and the contract defined in the swagger spec
608
func (c *Context) APIHandler(builder Builder) http.Handler {
609
	b := builder
610
	if b == nil {
611
		b = PassthroughBuilder
612
	}
613

614
	var title string
615
	sp := c.spec.Spec()
616
	if sp != nil && sp.Info != nil && sp.Info.Title != "" {
617
		title = sp.Info.Title
618
	}
619

620
	redocOpts := RedocOpts{
621
		BasePath: c.BasePath(),
622
		Title:    title,
623
	}
624

625
	return Spec("", c.spec.Raw(), Redoc(redocOpts, c.RoutesHandler(b)))
626
}
627

628
// RoutesHandler returns a handler to serve the API, just the routes and the contract defined in the swagger spec
629
func (c *Context) RoutesHandler(builder Builder) http.Handler {
630
	b := builder
631
	if b == nil {
632
		b = PassthroughBuilder
633
	}
634
	return NewRouter(c, b(NewOperationExecutor(c)))
635
}
636

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.