diff --git a/Makefile b/Makefile index d64be40..5ad65a0 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,5 @@ +all: clean example test + image = go-swagger:strato id = $(shell id -u):$(shell id -g) @@ -17,9 +19,9 @@ test: go test ./... example: build clean - cd example && \ - $(swagger) generate server && \ - $(swagger) generate client && \ + cd example ; \ + $(swagger) generate server ; \ + $(swagger) generate client ; \ go generate ./... clean: diff --git a/auth/auth.go b/auth/auth.go deleted file mode 100644 index 55a13c7..0000000 --- a/auth/auth.go +++ /dev/null @@ -1,17 +0,0 @@ -package auth - -import ( - "context" - "net/http" -) - -// Auth functions -type Auth interface { - APIKey(token string) (interface{}, error) - Basic(user, password string) (interface{}, error) - OAuth2(token string, scopes []string) (interface{}, error) - // Authorize gets a request and return error if it is not authorized - Authorize(r *http.Request) error - // AuthStore is a function that stores authentication in the context object - Store(context.Context, interface{}) context.Context -} diff --git a/example/restapi/configure_swagger_petstore.go b/example/restapi/configure_swagger_petstore.go index f2f96bb..282f163 100644 --- a/example/restapi/configure_swagger_petstore.go +++ b/example/restapi/configure_swagger_petstore.go @@ -51,16 +51,18 @@ type Config struct { // InnerMiddleware is for the handler executors. These do not apply to the swagger.json document. // The middleware executes after routing but before authentication, binding and validation InnerMiddleware func(http.Handler) http.Handler - Auth Auth -} -// Auth functions -type Auth interface { - APIKey(token string) (interface{}, error) - Basic(user, password string) (interface{}, error) - OAuth2(token string, scopes []string) (interface{}, error) - // AuthStore is a function that stores authentication in the context object - Store(context.Context, interface{}) context.Context + // Authorizer is used to authorize a request after the Auth function was called using the "Auth*" functions + // and the principal was stored in the context using the "StoreAuth" function. + Authorizer func(*http.Request) error + + // StoreAuth is used to store a principal in the request context. + // After storing the principal in the context, one can get it from the context in the business logic + // using a dedicated typed function. + StoreAuth func(context.Context, interface{}) context.Context + + // AuthKey Applies when the "Cookie" header is set + AuthKey func(token string) (interface{}, error) } // Handler returns an http.Handler given the handler configuration @@ -76,65 +78,72 @@ func Handler(c Config) (http.Handler, error) { api.JSONConsumer = runtime.JSONConsumer() api.JSONProducer = runtime.JSONProducer() - // Applies when the "Cookie" header is set - if c.Auth == nil || c.Auth.APIKey == nil { - return nil, fmt.Errorf("APIKey Authenticator was not defined") - } - if c.Auth == nil || c.Auth.Store == nil { - return nil, fmt.Errorf("Auth store function was not defined") - } api.KeyAuth = func(token string) (interface{}, error) { - return c.Auth.APIKey(token) + return c.AuthKey(token) } - // Set your custom authorizer if needed. Default one is security.Authorized() - // Expected interface runtime.Authorizer - // - // Example: - // api.APIAuthorizer = security.Authorized() + api.APIAuthorizer = &authorizer{authorize: c.Authorizer, store: c.StoreAuth} api.StoreInventoryGetHandler = store.InventoryGetHandlerFunc(func(params store.InventoryGetParams, principal interface{}) middleware.Responder { ctx := params.HTTPRequest.Context() - ctx = c.Auth.Store(ctx, principal) + if c.StoreAuth != nil { + ctx = c.StoreAuth(ctx, principal) + } return c.StoreAPI.InventoryGet(ctx, params) }) api.StoreOrderCreateHandler = store.OrderCreateHandlerFunc(func(params store.OrderCreateParams, principal interface{}) middleware.Responder { ctx := params.HTTPRequest.Context() - ctx = c.Auth.Store(ctx, principal) + if c.StoreAuth != nil { + ctx = c.StoreAuth(ctx, principal) + } return c.StoreAPI.OrderCreate(ctx, params) }) api.StoreOrderDeleteHandler = store.OrderDeleteHandlerFunc(func(params store.OrderDeleteParams, principal interface{}) middleware.Responder { ctx := params.HTTPRequest.Context() - ctx = c.Auth.Store(ctx, principal) + if c.StoreAuth != nil { + ctx = c.StoreAuth(ctx, principal) + } return c.StoreAPI.OrderDelete(ctx, params) }) api.StoreOrderGetHandler = store.OrderGetHandlerFunc(func(params store.OrderGetParams, principal interface{}) middleware.Responder { ctx := params.HTTPRequest.Context() - ctx = c.Auth.Store(ctx, principal) + if c.StoreAuth != nil { + ctx = c.StoreAuth(ctx, principal) + } return c.StoreAPI.OrderGet(ctx, params) }) api.PetPetCreateHandler = pet.PetCreateHandlerFunc(func(params pet.PetCreateParams, principal interface{}) middleware.Responder { ctx := params.HTTPRequest.Context() - ctx = c.Auth.Store(ctx, principal) + if c.StoreAuth != nil { + ctx = c.StoreAuth(ctx, principal) + } return c.PetAPI.PetCreate(ctx, params) }) api.PetPetDeleteHandler = pet.PetDeleteHandlerFunc(func(params pet.PetDeleteParams, principal interface{}) middleware.Responder { ctx := params.HTTPRequest.Context() - ctx = c.Auth.Store(ctx, principal) + if c.StoreAuth != nil { + ctx = c.StoreAuth(ctx, principal) + } return c.PetAPI.PetDelete(ctx, params) }) api.PetPetGetHandler = pet.PetGetHandlerFunc(func(params pet.PetGetParams, principal interface{}) middleware.Responder { ctx := params.HTTPRequest.Context() - ctx = c.Auth.Store(ctx, principal) + if c.StoreAuth != nil { + ctx = c.StoreAuth(ctx, principal) + } return c.PetAPI.PetGet(ctx, params) }) api.PetPetListHandler = pet.PetListHandlerFunc(func(params pet.PetListParams, principal interface{}) middleware.Responder { ctx := params.HTTPRequest.Context() - ctx = c.Auth.Store(ctx, principal) + if c.StoreAuth != nil { + ctx = c.StoreAuth(ctx, principal) + } return c.PetAPI.PetList(ctx, params) }) api.PetPetUpdateHandler = pet.PetUpdateHandlerFunc(func(params pet.PetUpdateParams, principal interface{}) middleware.Responder { ctx := params.HTTPRequest.Context() - ctx = c.Auth.Store(ctx, principal) + if c.StoreAuth != nil { + ctx = c.StoreAuth(ctx, principal) + } return c.PetAPI.PetUpdate(ctx, params) }) api.ServerShutdown = func() {} @@ -144,10 +153,10 @@ func Handler(c Config) (http.Handler, error) { // Query parse functions for all the models // Those can be used to extract database query from the http path's query string var ( - OrderQueryParse = query.MustNewBuilder(&query.Config{Model: models.Order{}}).ParseRequest PetQueryParse = query.MustNewBuilder(&query.Config{Model: models.Pet{}}).ParseRequest CategoryQueryParse = query.MustNewBuilder(&query.Config{Model: models.Category{}}).ParseRequest TagQueryParse = query.MustNewBuilder(&query.Config{Model: models.Tag{}}).ParseRequest + OrderQueryParse = query.MustNewBuilder(&query.Config{Model: models.Order{}}).ParseRequest ) // swaggerCopy copies the swagger json to prevent data races in runtime @@ -156,3 +165,20 @@ func swaggerCopy(orig json.RawMessage) json.RawMessage { copy(c, orig) return c } + +// authorizer is a helper struct to implement the runtime.Authorizer interface. +type authorizer struct { + authorize func(*http.Request) error + store func(context.Context, interface{}) context.Context +} + +func (a *authorizer) Authorize(req *http.Request, principal interface{}) error { + ctx := req.Context() + if a.store != nil { + ctx = a.store(ctx, principal) + } + if a.authorize == nil { + return nil + } + return a.authorize(req.WithContext(ctx)) +} diff --git a/templates/server/configureapi.gotmpl b/templates/server/configureapi.gotmpl index aea6492..ddeb408 100644 --- a/templates/server/configureapi.gotmpl +++ b/templates/server/configureapi.gotmpl @@ -53,7 +53,30 @@ type Config struct { // InnerMiddleware is for the handler executors. These do not apply to the swagger.json document. // The middleware executes after routing but before authentication, binding and validation InnerMiddleware func(http.Handler) http.Handler - Auth auth.Auth + + // Authorizer is used to authorize a request after the Auth function was called using the "Auth*" functions + // and the principal was stored in the context using the "StoreAuth" function. + Authorizer func(*http.Request) error + + // StoreAuth is used to store a principal in the request context. + // After storing the principal in the context, one can get it from the context in the business logic + // using a dedicated typed function. + StoreAuth func(context.Context, interface{}) context.Context + + {{ range .SecurityDefinitions -}} + {{ if .IsBasicAuth -}} + // Auth{{ pascalize .ID }} for basic authentication + Auth{{ pascalize .ID }} func(user string, pass string) + {{ end -}} + {{ if .IsAPIKeyAuth -}} + // Auth{{ pascalize .ID }} Applies when the "{{ .Name }}" {{ .Source }} is set + Auth{{ pascalize .ID }} func(token string) (interface{}, error) + {{ end }} + {{ if .IsOAuth2 -}} + // Auth{{ pascalize .ID }} For OAuth2 authentication + Auth{{ pascalize .ID }} func(token string, scopes []string) (interface{}, error) + {{ end -}} + {{ end -}} } // Handler returns an http.Handler given the handler configuration @@ -88,21 +111,13 @@ func Handler(c Config) (http.Handler, error) { {{ range .SecurityDefinitions -}} {{ if .IsBasicAuth -}} - // Applies when the Authorization header is set with the Basic scheme - if c.Auth == nil { - return nil, fmt.Errorf("Authenticator was not defined") - } api.{{ pascalize .ID }}Auth = func(user string, pass string) ({{if not ( eq .Principal "interface{}" )}}*{{ end }}{{.Principal}}, error) { - return c.Auth.Basic(user pass) + return c.Auth{{ pascalize .ID }}(user, pass) } {{ end -}} {{ if .IsAPIKeyAuth -}} - // Applies when the "{{ .Name }}" {{ .Source }} is set - if c.Auth == nil { - return nil, fmt.Errorf("Authenticator was not defined") - } api.{{ pascalize .ID }}Auth = func(token string) ({{if not ( eq .Principal "interface{}" )}}*{{ end }}{{.Principal}}, error) { - return c.Auth.APIKey(token) + return c.Auth{{ pascalize .ID }}(token) } {{ end }} {{ if .IsOAuth2 -}} @@ -110,20 +125,22 @@ func Handler(c Config) (http.Handler, error) { return nil, fmt.Errorf("Authenticator was not defined") } api.{{ pascalize .ID }}Auth = func(token string, scopes []string) ({{if not ( eq .Principal "interface{}" )}}*{{ end }}{{.Principal}}, error) { - return c.Auth.OAuth2(token, scopes) + return c.Auth{{ pascalize .ID }}(token, scopes) } {{ end -}} {{ end -}} {{ if .SecurityDefinitions -}} - api.APIAuthorizer = &authorizer{Auth: c.Auth} + api.APIAuthorizer = &authorizer{authorize: c.Authorizer, store: c.StoreAuth} {{ end -}} {{ range .Operations -}} api.{{if ne .Package $package}}{{pascalize .Package}}{{end}}{{ pascalize .Name }}Handler = {{.Package}}.{{ pascalize .Name }}HandlerFunc(func({{ if .WithContext }}ctx context.Context, {{ end }}params {{.Package}}.{{ pascalize .Name }}Params{{if .Authorized}}, principal interface{}{{end}}) middleware.Responder { ctx := params.HTTPRequest.Context() {{ if .Authorized -}} - ctx = c.Auth.Store(ctx, principal) + if c.StoreAuth != nil { + ctx = c.StoreAuth(ctx, principal) + } {{ end -}} return c.{{pascalize .Package}}API.{{pascalize .Name}}(ctx, params) }) @@ -154,11 +171,17 @@ func swaggerCopy(orig json.RawMessage) json.RawMessage { // authorizer is a helper struct to implement the runtime.Authorizer interface. type authorizer struct { - auth.Auth + authorize func(*http.Request) error + store func(context.Context, interface{}) context.Context } func (a *authorizer) Authorize(req *http.Request, principal interface{}) error { ctx := req.Context() - ctx = a.Store(ctx, principal) - return a.Auth.Authorize(req.WithContext(ctx)) + if a.store != nil { + ctx = a.store(ctx, principal) + } + if a.authorize == nil { + return nil + } + return a.authorize(req.WithContext(ctx)) }