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