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" 20 fpath "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 { 41 Name string 42 Value 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 { 50 vv, _, _ := r.GetOK(name) 51 if len(vv) > 0 { 52 return vv[len(vv)-1] 53 } 54 return "" 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) { 62 for _, p := range r { 63 if p.Name == name { 64 return []string{p.Value}, true, p.Value != "" 65 } 66 } 67 return nil, false, false 68} 69 70// NewRouter creates a new context aware router middleware 71func NewRouter(ctx *Context, next http.Handler) http.Handler { 72 if ctx.router == nil { 73 ctx.router = DefaultRouter(ctx.spec, ctx.api) 74 } 75 76 return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { 77 if _, rCtx, ok := ctx.RouteInfo(r); ok { 78 next.ServeHTTP(rw, rCtx) 79 return 80 } 81 82 // Not found, check if it exists in the other methods first 83 if others := ctx.AllowedMethods(r); len(others) > 0 { 84 ctx.Respond(rw, r, ctx.analyzer.RequiredProduces(), nil, errors.MethodNotAllowed(r.Method, others)) 85 return 86 } 87 88 ctx.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 { 95 HandlerFor(string, string) (http.Handler, bool) 96 ServeErrorFor(string) func(http.ResponseWriter, *http.Request, error) 97 ConsumersFor([]string) map[string]runtime.Consumer 98 ProducersFor([]string) map[string]runtime.Producer 99 AuthenticatorsFor(map[string]spec.SecurityScheme) map[string]runtime.Authenticator 100 Authorizer() runtime.Authorizer 101 Formats() strfmt.Registry 102 DefaultProduces() string 103 DefaultConsumes() string 104} 105 106// Router represents a swagger aware router 107type Router interface { 108 Lookup(method, path string) (*MatchedRoute, bool) 109 OtherMethods(method, path string) []string 110} 111 112type defaultRouteBuilder struct { 113 spec *loads.Document 114 analyzer *analysis.Spec 115 api RoutableAPI 116 records map[string][]denco.Record 117} 118 119type defaultRouter struct { 120 spec *loads.Document 121 routers map[string]*denco.Router 122} 123 124func newDefaultRouteBuilder(spec *loads.Document, api RoutableAPI) *defaultRouteBuilder { 125 return &defaultRouteBuilder{ 126 spec: spec, 127 analyzer: analysis.New(spec.Spec()), 128 api: api, 129 records: 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 { 135 builder := newDefaultRouteBuilder(spec, api) 136 if spec != nil { 137 for method, paths := range builder.analyzer.Operations() { 138 for path, operation := range paths { 139 fp := fpath.Join(spec.BasePath(), path) 140 debugLog("adding route %s %s %q", method, fp, operation.ID) 141 builder.AddRoute(method, fp, operation) 142 } 143 } 144 } 145 return 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 { 152 Authenticator map[string]runtime.Authenticator 153 Schemes []string 154 Scopes map[string][]string 155 allScopes []string 156 commonScopes []string 157 allowAnonymous bool 158} 159 160func (ra *RouteAuthenticator) AllowsAnonymous() bool { 161 return 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 { 167 return 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 { 173 return ra.commonScopes 174} 175 176// Authenticate Authenticator interface implementation 177func (ra *RouteAuthenticator) Authenticate(req *http.Request, route *MatchedRoute) (bool, interface{}, error) { 178 if ra.allowAnonymous { 179 route.Authenticator = ra 180 return true, nil, nil 181 } 182 // iterate in proper order 183 var lastResult interface{} 184 for _, scheme := range ra.Schemes { 185 if authenticator, ok := ra.Authenticator[scheme]; ok { 186 applies, princ, err := authenticator.Authenticate(&security.ScopedAuthRequest{ 187 Request: req, 188 RequiredScopes: ra.Scopes[scheme], 189 }) 190 if !applies { 191 return false, nil, nil 192 } 193 if err != nil { 194 route.Authenticator = ra 195 return true, nil, err 196 } 197 lastResult = princ 198 } 199 } 200 route.Authenticator = ra 201 return true, lastResult, nil 202} 203 204func stringSliceUnion(slices ...[]string) []string { 205 unique := make(map[string]struct{}) 206 var result []string 207 for _, slice := range slices { 208 for _, entry := range slice { 209 if _, ok := unique[entry]; ok { 210 continue 211 } 212 unique[entry] = struct{}{} 213 result = append(result, entry) 214 } 215 } 216 return result 217} 218 219func stringSliceIntersection(slices ...[]string) []string { 220 unique := make(map[string]int) 221 var intersection []string 222 223 total := len(slices) 224 var emptyCnt int 225 for _, slice := range slices { 226 if len(slice) == 0 { 227 emptyCnt++ 228 continue 229 } 230 231 for _, entry := range slice { 232 unique[entry]++ 233 if unique[entry] == total-emptyCnt { // this entry appeared in all the non-empty slices 234 intersection = append(intersection, entry) 235 } 236 } 237 } 238 239 return 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 { 247 for _, ra := range ras { 248 if ra.AllowsAnonymous() { 249 return true 250 } 251 } 252 return 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) { 257 var lastError error 258 var allowsAnon bool 259 var anonAuth RouteAuthenticator 260 261 for _, ra := range ras { 262 if ra.AllowsAnonymous() { 263 anonAuth = ra 264 allowsAnon = true 265 continue 266 } 267 applies, usr, err := ra.Authenticate(req, route) 268 if !applies || err != nil || usr == nil { 269 if err != nil { 270 lastError = err 271 } 272 continue 273 } 274 return applies, usr, nil 275 } 276 277 if allowsAnon && lastError == nil { 278 route.Authenticator = &anonAuth 279 return true, nil, lastError 280 } 281 return lastError != nil, nil, lastError 282} 283 284type routeEntry struct { 285 PathPattern string 286 BasePath string 287 Operation *spec.Operation 288 Consumes []string 289 Consumers map[string]runtime.Consumer 290 Produces []string 291 Producers map[string]runtime.Producer 292 Parameters map[string]spec.Parameter 293 Handler http.Handler 294 Formats strfmt.Registry 295 Binder *UntypedRequestBinder 296 Authenticators RouteAuthenticators 297 Authorizer runtime.Authorizer 298} 299 300// MatchedRoute represents the route that was matched in this request 301type MatchedRoute struct { 302 routeEntry 303 Params RouteParams 304 Consumer runtime.Consumer 305 Producer runtime.Producer 306 Authenticator *RouteAuthenticator 307} 308 309// HasAuth returns true when the route has a security requirement defined 310func (m *MatchedRoute) HasAuth() bool { 311 return len(m.Authenticators) > 0 312} 313 314// NeedsAuth returns true when the request still 315// needs to perform authentication 316func (m *MatchedRoute) NeedsAuth() bool { 317 return m.HasAuth() && m.Authenticator == nil 318} 319 320func (d *defaultRouter) Lookup(method, path string) (*MatchedRoute, bool) { 321 mth := strings.ToUpper(method) 322 debugLog("looking up route for %s %s", method, path) 323 if Debug { 324 if len(d.routers) == 0 { 325 debugLog("there are no known routers") 326 } 327 for meth := range d.routers { 328 debugLog("got a router for %s", meth) 329 } 330 } 331 if router, ok := d.routers[mth]; ok { 332 if m, rp, ok := router.Lookup(fpath.Clean(path)); ok && m != nil { 333 if entry, ok := m.(*routeEntry); ok { 334 debugLog("found a route for %s %s with %d parameters", method, path, len(entry.Parameters)) 335 var params RouteParams 336 for _, p := range rp { 337 v, err := pathUnescape(p.Value) 338 if err != nil { 339 debugLog("failed to escape %q: %v", p.Value, err) 340 v = 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 344 if 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 346 ep := strings.Split(entry.PathPattern[xpos:], "/")[0] 347 pnames, pvalues := decodeCompositParams(p.Name, v, ep, nil, nil) 348 for i, pname := range pnames { 349 params = append(params, RouteParam{Name: pname, Value: pvalues[i]}) 350 } 351 } else { 352 // use the parameter directly 353 params = append(params, RouteParam{Name: p.Name, Value: v}) 354 } 355 } 356 return &MatchedRoute{routeEntry: *entry, Params: params}, true 357 } 358 } else { 359 debugLog("couldn't find a route by path for %s %s", method, path) 360 } 361 } else { 362 debugLog("couldn't find a route by method for %s %s", method, path) 363 } 364 return nil, false 365} 366 367func (d *defaultRouter) OtherMethods(method, path string) []string { 368 mn := strings.ToUpper(method) 369 var methods []string 370 for k, v := range d.routers { 371 if k != mn { 372 if _, _, ok := v.Lookup(fpath.Clean(path)); ok { 373 methods = append(methods, k) 374 continue 375 } 376 } 377 } 378 return 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) { 385 pleft := strings.Index(pattern, "{") 386 names = append(names, name) 387 if pleft < 0 { 388 if strings.HasSuffix(value, pattern) { 389 values = append(values, value[:len(value)-len(pattern)]) 390 } else { 391 values = append(values, "") 392 } 393 } else { 394 toskip := pattern[:pleft] 395 pright := strings.Index(pattern, "}") 396 vright := strings.Index(value, toskip) 397 if vright >= 0 { 398 values = append(values, value[:vright]) 399 } else { 400 values = append(values, "") 401 value = "" 402 } 403 return decodeCompositParams(pattern[pleft+1:pright], value[vright+len(toskip):], pattern[pright+1:], names, values) 404 } 405 return names, values 406} 407 408func (d *defaultRouteBuilder) AddRoute(method, path string, operation *spec.Operation) { 409 mn := strings.ToUpper(method) 410 411 bp := fpath.Clean(d.spec.BasePath()) 412 if len(bp) > 0 && bp[len(bp)-1] == '/' { 413 bp = bp[:len(bp)-1] 414 } 415 416 debugLog("operation: %#v", *operation) 417 if handler, ok := d.api.HandlerFor(method, strings.TrimPrefix(path, bp)); ok { 418 consumes := d.analyzer.ConsumesFor(operation) 419 produces := d.analyzer.ProducesFor(operation) 420 parameters := d.analyzer.ParamsFor(method, strings.TrimPrefix(path, bp)) 421 422 // add API defaults if not part of the spec 423 if defConsumes := d.api.DefaultConsumes(); defConsumes != "" && !swag.ContainsStringsCI(consumes, defConsumes) { 424 consumes = append(consumes, defConsumes) 425 } 426 427 if defProduces := d.api.DefaultProduces(); defProduces != "" && !swag.ContainsStringsCI(produces, defProduces) { 428 produces = append(produces, defProduces) 429 } 430 431 record := denco.NewRecord(pathConverter.ReplaceAllString(path, ":$1"), &routeEntry{ 432 BasePath: bp, 433 PathPattern: path, 434 Operation: operation, 435 Handler: handler, 436 Consumes: consumes, 437 Produces: produces, 438 Consumers: d.api.ConsumersFor(normalizeOffers(consumes)), 439 Producers: d.api.ProducersFor(normalizeOffers(produces)), 440 Parameters: parameters, 441 Formats: d.api.Formats(), 442 Binder: NewUntypedRequestBinder(parameters, d.spec.Spec(), d.api.Formats()), 443 Authenticators: d.buildAuthenticators(operation), 444 Authorizer: d.api.Authorizer(), 445 }) 446 d.records[mn] = append(d.records[mn], record) 447 } 448} 449 450func (d *defaultRouteBuilder) buildAuthenticators(operation *spec.Operation) RouteAuthenticators { 451 requirements := d.analyzer.SecurityRequirementsFor(operation) 452 var auths []RouteAuthenticator 453 for _, reqs := range requirements { 454 var schemes []string 455 scopes := make(map[string][]string, len(reqs)) 456 var scopeSlices [][]string 457 for _, req := range reqs { 458 schemes = append(schemes, req.Name) 459 scopes[req.Name] = req.Scopes 460 scopeSlices = append(scopeSlices, req.Scopes) 461 } 462 463 definitions := d.analyzer.SecurityDefinitionsForRequirements(reqs) 464 authenticators := d.api.AuthenticatorsFor(definitions) 465 auths = append(auths, RouteAuthenticator{ 466 Authenticator: authenticators, 467 Schemes: schemes, 468 Scopes: scopes, 469 allScopes: stringSliceUnion(scopeSlices...), 470 commonScopes: stringSliceIntersection(scopeSlices...), 471 allowAnonymous: len(reqs) == 1 && reqs[0].Name == "", 472 }) 473 } 474 return auths 475} 476 477func (d *defaultRouteBuilder) Build() *defaultRouter { 478 routers := make(map[string]*denco.Router) 479 for method, records := range d.records { 480 router := denco.New() 481 _ = router.Build(records) 482 routers[method] = router 483 } 484 return &defaultRouter{ 485 spec: d.spec, 486 routers: routers, 487 } 488} 489