From e907d24e3bfafca5b53a0f7acb935c152d6016f7 Mon Sep 17 00:00:00 2001 From: Ben Toogood Date: Tue, 7 Apr 2020 19:29:26 +0100 Subject: [PATCH] API Wrappers --- api/resolver/resolver.go | 6 -- api/server/auth/auth.go | 117 --------------------------------------- api/server/http/http.go | 7 ++- api/server/options.go | 31 ++++++----- 4 files changed, 22 insertions(+), 139 deletions(-) delete mode 100644 api/server/auth/auth.go diff --git a/api/resolver/resolver.go b/api/resolver/resolver.go index 99a10802..12854b19 100644 --- a/api/resolver/resolver.go +++ b/api/resolver/resolver.go @@ -11,12 +11,6 @@ var ( ErrInvalidPath = errors.New("invalid path") ) -// NamespaceResolver resolves request to the namespace -type NamespaceResolver interface { - Resolve(r *http.Request) string - String() string -} - // Resolver resolves requests to endpoints type Resolver interface { Resolve(r *http.Request) (*Endpoint, error) diff --git a/api/server/auth/auth.go b/api/server/auth/auth.go deleted file mode 100644 index deffa3dc..00000000 --- a/api/server/auth/auth.go +++ /dev/null @@ -1,117 +0,0 @@ -package auth - -import ( - "context" - "fmt" - "net/http" - "net/url" - "strings" - - "github.com/micro/go-micro/v2/api/resolver" - "github.com/micro/go-micro/v2/auth" - "github.com/micro/go-micro/v2/logger" -) - -// CombinedAuthHandler wraps a server and authenticates requests -func CombinedAuthHandler(r resolver.Resolver, nr resolver.NamespaceResolver, h http.Handler) http.Handler { - return authHandler{ - handler: h, - resolver: r, - nsResolver: nr, - auth: auth.DefaultAuth, - } -} - -type authHandler struct { - handler http.Handler - auth auth.Auth - resolver resolver.Resolver - nsResolver resolver.NamespaceResolver -} - -func (h authHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { - // Determine the namespace and set it in the header - namespace := h.nsResolver.Resolve(req) - req.Header.Set(auth.NamespaceKey, namespace) - - // Extract the token from the request - var token string - if header := req.Header.Get("Authorization"); len(header) > 0 { - // Extract the auth token from the request - if strings.HasPrefix(header, auth.BearerScheme) { - token = header[len(auth.BearerScheme):] - } - } else { - // Get the token out the cookies if not provided in headers - if c, err := req.Cookie("micro-token"); err == nil && c != nil { - token = strings.TrimPrefix(c.Value, auth.TokenCookieName+"=") - req.Header.Set("Authorization", auth.BearerScheme+token) - } - } - - // Get the account using the token, fallback to a blank account - // since some endpoints can be unauthenticated, so the lack of an - // account doesn't necesserially mean a forbidden request - acc, err := h.auth.Inspect(token) - if err != nil { - acc = &auth.Account{} - } - - // Determine the name of the service being requested - endpoint, err := h.resolver.Resolve(req) - if err == resolver.ErrInvalidPath || err == resolver.ErrNotFound { - // a file not served by the resolver has been requested (e.g. favicon.ico) - endpoint = &resolver.Endpoint{Path: req.URL.Path} - } else if err != nil { - logger.Error(err) - http.Error(w, err.Error(), 500) - return - } else { - // set the endpoint in the context so it can be used to resolve - // the request later - ctx := context.WithValue(req.Context(), resolver.Endpoint{}, endpoint) - *req = *req.Clone(ctx) - } - - // construct the resource name, e.g. home => go.micro.web.home - resName := namespace - if len(endpoint.Name) > 0 { - resName = namespace + "." + endpoint.Name - } - - // determine the resource path. there is an inconsistency in how resolvers - // use method, some use it as Users.ReadUser (the rpc method), and others - // use it as the HTTP method, e.g GET. TODO: Refactor this to make it consistent. - resEndpoint := endpoint.Path - if len(endpoint.Path) == 0 { - resEndpoint = endpoint.Method - } - - // Perform the verification check to see if the account has access to - // the resource they're requesting - res := &auth.Resource{Type: "service", Name: resName, Endpoint: resEndpoint, Namespace: namespace} - if err := h.auth.Verify(acc, res); err == nil { - // The account has the necessary permissions to access the resource - h.handler.ServeHTTP(w, req) - return - } - - // The account is set, but they don't have enough permissions, hence - // we return a forbidden error. - if len(acc.ID) > 0 { - http.Error(w, "Forbidden request", 403) - return - } - - // If there is no auth login url set, 401 - loginURL := h.auth.Options().LoginURL - if loginURL == "" { - http.Error(w, "unauthorized request", 401) - return - } - - // Redirect to the login path - params := url.Values{"redirect_to": {req.URL.String()}} - loginWithRedirect := fmt.Sprintf("%v?%v", loginURL, params.Encode()) - http.Redirect(w, req, loginWithRedirect, http.StatusTemporaryRedirect) -} diff --git a/api/server/http/http.go b/api/server/http/http.go index c35d24b7..6615232d 100644 --- a/api/server/http/http.go +++ b/api/server/http/http.go @@ -10,7 +10,6 @@ import ( "github.com/gorilla/handlers" "github.com/micro/go-micro/v2/api/server" - "github.com/micro/go-micro/v2/api/server/auth" "github.com/micro/go-micro/v2/api/server/cors" "github.com/micro/go-micro/v2/logger" ) @@ -53,7 +52,11 @@ func (s *httpServer) Init(opts ...server.Option) error { func (s *httpServer) Handle(path string, handler http.Handler) { h := handlers.CombinedLoggingHandler(os.Stdout, handler) - h = auth.CombinedAuthHandler(s.opts.Resolver, s.opts.NamespaceResolver, handler) + + // apply the wrappers, e.g. auth + for _, wrapper := range s.opts.Wrappers { + h = wrapper(h) + } if s.opts.EnableCORS { h = cors.CombinedCORSHandler(h) diff --git a/api/server/options.go b/api/server/options.go index 9aa14579..6d87f543 100644 --- a/api/server/options.go +++ b/api/server/options.go @@ -2,6 +2,7 @@ package server import ( "crypto/tls" + "net/http" "github.com/micro/go-micro/v2/api/resolver" "github.com/micro/go-micro/v2/api/server/acme" @@ -10,14 +11,22 @@ import ( type Option func(o *Options) type Options struct { - EnableACME bool - EnableCORS bool - ACMEProvider acme.Provider - EnableTLS bool - ACMEHosts []string - TLSConfig *tls.Config - Resolver resolver.Resolver - NamespaceResolver resolver.NamespaceResolver + EnableACME bool + EnableCORS bool + ACMEProvider acme.Provider + EnableTLS bool + ACMEHosts []string + TLSConfig *tls.Config + Resolver resolver.Resolver + Wrappers []Wrapper +} + +type Wrapper func(h http.Handler) http.Handler + +func WrapHandler(w Wrapper) Option { + return func(o *Options) { + o.Wrappers = append(o.Wrappers, w) + } } func EnableCORS(b bool) Option { @@ -61,9 +70,3 @@ func Resolver(r resolver.Resolver) Option { o.Resolver = r } } - -func NamespaceResolver(r resolver.NamespaceResolver) Option { - return func(o *Options) { - o.NamespaceResolver = r - } -}