diff --git a/api/resolver/path/path.go b/api/resolver/path/path.go index fec80f62..c4d41fc6 100644 --- a/api/resolver/path/path.go +++ b/api/resolver/path/path.go @@ -2,7 +2,6 @@ package path import ( - "errors" "net/http" "strings" @@ -13,7 +12,7 @@ type Resolver struct{} func (r *Resolver) Resolve(req *http.Request) (*resolver.Endpoint, error) { if req.URL.Path == "/" { - return nil, errors.New("unknown name") + return nil, resolver.ErrNotFound } parts := strings.Split(req.URL.Path[1:], "/") return &resolver.Endpoint{ diff --git a/api/resolver/resolver.go b/api/resolver/resolver.go index 2e23d62e..12854b19 100644 --- a/api/resolver/resolver.go +++ b/api/resolver/resolver.go @@ -2,9 +2,15 @@ package resolver import ( + "errors" "net/http" ) +var ( + ErrNotFound = errors.New("not found") + ErrInvalidPath = errors.New("invalid path") +) + // 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 index a278ebda..13b3c999 100644 --- a/api/server/auth/auth.go +++ b/api/server/auth/auth.go @@ -7,21 +7,34 @@ import ( "net/url" "strings" + "github.com/micro/go-micro/v2/api/resolver" + "github.com/micro/go-micro/v2/api/resolver/path" "github.com/micro/go-micro/v2/auth" "github.com/micro/go-micro/v2/logger" ) // CombinedAuthHandler wraps a server and authenticates requests -func CombinedAuthHandler(h http.Handler) http.Handler { +func CombinedAuthHandler(namespace string, r resolver.Resolver, h http.Handler) http.Handler { + if r == nil { + r = path.NewResolver() + } + if len(namespace) == 0 { + namespace = "go.micro" + } + return authHandler{ - handler: h, - auth: auth.DefaultAuth, + handler: h, + resolver: r, + auth: auth.DefaultAuth, + namespace: namespace, } } type authHandler struct { - handler http.Handler - auth auth.Auth + handler http.Handler + auth auth.Auth + resolver resolver.Resolver + namespace string } func (h authHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { @@ -65,17 +78,35 @@ func (h authHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { w.WriteHeader(http.StatusForbidden) } + // 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 { + w.WriteHeader(http.StatusInternalServerError) + return + } + + // construct the resource name, e.g. home => go.micro.web.home + resName := h.namespace + if len(endpoint.Name) > 0 { + resName = resName + "." + 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 - err = h.auth.Verify(acc, &auth.Resource{ - Type: "service", - Name: "go.micro.web", - Endpoint: req.URL.Path, - Namespace: namespace, - }) - - // The account has the necessary permissions to access the resource - if err == nil { + 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 } diff --git a/api/server/http/http.go b/api/server/http/http.go index 0af91256..2599d2db 100644 --- a/api/server/http/http.go +++ b/api/server/http/http.go @@ -8,10 +8,9 @@ import ( "os" "sync" - "github.com/micro/go-micro/v2/api/server/auth" - "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" ) @@ -25,9 +24,14 @@ type httpServer struct { exit chan chan error } -func NewServer(address string) server.Server { +func NewServer(address string, opts ...server.Option) server.Server { + var options server.Options + for _, o := range opts { + o(&options) + } + return &httpServer{ - opts: server.Options{}, + opts: options, mux: http.NewServeMux(), address: address, exit: make(chan chan error), @@ -49,7 +53,7 @@ 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(handler) + h = auth.CombinedAuthHandler(s.opts.Namespace, s.opts.Resolver, handler) if s.opts.EnableCORS { h = cors.CombinedCORSHandler(h) diff --git a/api/server/options.go b/api/server/options.go index 99be1a03..5d167ced 100644 --- a/api/server/options.go +++ b/api/server/options.go @@ -3,6 +3,7 @@ package server import ( "crypto/tls" + "github.com/micro/go-micro/v2/api/resolver" "github.com/micro/go-micro/v2/api/server/acme" ) @@ -15,6 +16,8 @@ type Options struct { EnableTLS bool ACMEHosts []string TLSConfig *tls.Config + Namespace string + Resolver resolver.Resolver } func EnableCORS(b bool) Option { @@ -52,3 +55,15 @@ func TLSConfig(t *tls.Config) Option { o.TLSConfig = t } } + +func Namespace(n string) Option { + return func(o *Options) { + o.Namespace = n + } +} + +func Resolver(r resolver.Resolver) Option { + return func(o *Options) { + o.Resolver = r + } +}