From 7206d5f964ee89a1b09c3566bbd47674e847b5bf Mon Sep 17 00:00:00 2001 From: Ben Toogood Date: Tue, 7 Apr 2020 09:40:40 +0100 Subject: [PATCH 01/15] Add Namespace to CombinedAuthHandler --- api/server/auth/auth.go | 80 ++++++++++++++++++------------------ api/server/auth/auth_test.go | 34 +++++++++++++++ api/server/http/http.go | 2 +- api/server/options.go | 23 +++++++---- 4 files changed, 91 insertions(+), 48 deletions(-) create mode 100644 api/server/auth/auth_test.go diff --git a/api/server/auth/auth.go b/api/server/auth/auth.go index f0e38562..07d28068 100644 --- a/api/server/auth/auth.go +++ b/api/server/auth/auth.go @@ -15,38 +15,32 @@ import ( ) // CombinedAuthHandler wraps a server and authenticates requests -func CombinedAuthHandler(namespace string, r resolver.Resolver, h http.Handler) http.Handler { +func CombinedAuthHandler(prefix, 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, - resolver: r, - auth: auth.DefaultAuth, - namespace: namespace, + handler: h, + resolver: r, + auth: auth.DefaultAuth, + servicePrefix: prefix, + namespace: namespace, } } type authHandler struct { - handler http.Handler - auth auth.Auth - resolver resolver.Resolver - namespace string + handler http.Handler + auth auth.Auth + resolver resolver.Resolver + namespace string + servicePrefix string } func (h authHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { - // Determine the namespace - namespace, err := namespaceFromRequest(req) - if err != nil { - logger.Error(err) - namespace = auth.DefaultNamespace - } - - // Set the namespace in the header + // Determine the namespace and set it in the header + namespace := h.namespaceFromRequest(req) + fmt.Printf("Namespace is %v\n", namespace) req.Header.Set(auth.NamespaceKey, namespace) // Extract the token from the request @@ -96,7 +90,7 @@ func (h authHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { } // construct the resource name, e.g. home => go.micro.web.home - resName := h.namespace + resName := h.servicePrefix if len(endpoint.Name) > 0 { resName = resName + "." + endpoint.Name } @@ -138,39 +132,47 @@ func (h authHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { http.Redirect(w, req, loginWithRedirect, http.StatusTemporaryRedirect) } -func namespaceFromRequest(req *http.Request) (string, error) { - // needed to tmp debug host in prod. will be removed. - logger.Infof("Host is '%v'; URL Host is '%v'; URL Hostname is '%v'", req.Host, req.URL.Host, req.URL.Hostname()) +func (h authHandler) namespaceFromRequest(req *http.Request) string { + // check to see what the provided namespace is, we only do + // domain mapping if the namespace is set to 'domain' + if h.namespace != "domain" { + return h.namespace + } // determine the host, e.g. dev.micro.mu:8080 - host := req.URL.Hostname() - if len(host) == 0 { - // fallback to req.Host - host, _, _ = net.SplitHostPort(req.Host) + var host string + if h, _, err := net.SplitHostPort(req.Host); err == nil { + host = h // host does contain a port + } else { + host = req.Host // host does not contain a port + } + + // check for the micro.mu domain + if strings.HasSuffix(host, "micro.mu") { + return auth.DefaultNamespace } // check for an ip address if net.ParseIP(host) != nil { - return auth.DefaultNamespace, nil + return auth.DefaultNamespace } // check for dev enviroment if host == "localhost" || host == "127.0.0.1" { - return auth.DefaultNamespace, nil + return auth.DefaultNamespace } // if host is not a subdomain, deturn default namespace comps := strings.Split(host, ".") - if len(comps) != 3 { - return auth.DefaultNamespace, nil + if len(comps) < 3 { + return auth.DefaultNamespace } - // check for the micro.mu domain - domain := fmt.Sprintf("%v.%v", comps[1], comps[2]) - if domain == "micro.mu" { - return auth.DefaultNamespace, nil + // return the reversed subdomain as the namespace + nComps := comps[0 : len(comps)-2] + for i := len(nComps)/2 - 1; i >= 0; i-- { + opp := len(nComps) - 1 - i + nComps[i], nComps[opp] = nComps[opp], nComps[i] } - - // return the subdomain as the host - return comps[0], nil + return strings.Join(nComps, ".") } diff --git a/api/server/auth/auth_test.go b/api/server/auth/auth_test.go new file mode 100644 index 00000000..cf454e28 --- /dev/null +++ b/api/server/auth/auth_test.go @@ -0,0 +1,34 @@ +package auth + +import ( + "net/http" + "testing" + + "github.com/micro/go-micro/v2/auth" +) + +func TestNamespaceFromRequest(t *testing.T) { + tt := []struct { + Host string + Namespace string + }{ + {Host: "micro.mu", Namespace: auth.DefaultNamespace}, + {Host: "web.micro.mu", Namespace: auth.DefaultNamespace}, + {Host: "api.micro.mu", Namespace: auth.DefaultNamespace}, + {Host: "myapp.com", Namespace: auth.DefaultNamespace}, + {Host: "staging.myapp.com", Namespace: "staging"}, + {Host: "staging.myapp.m3o.app", Namespace: "myapp.staging"}, + {Host: "127.0.0.1", Namespace: auth.DefaultNamespace}, + {Host: "localhost", Namespace: auth.DefaultNamespace}, + {Host: "81.151.101.146", Namespace: auth.DefaultNamespace}, + } + + for _, tc := range tt { + t.Run(tc.Host, func(t *testing.T) { + ns := namespaceFromRequest(&http.Request{Host: tc.Host}) + if ns != tc.Namespace { + t.Errorf("Expected namespace %v for host %v, actually got %v", tc.Namespace, tc.Host, ns) + } + }) + } +} diff --git a/api/server/http/http.go b/api/server/http/http.go index 2599d2db..02238aa9 100644 --- a/api/server/http/http.go +++ b/api/server/http/http.go @@ -53,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(s.opts.Namespace, s.opts.Resolver, handler) + h = auth.CombinedAuthHandler(s.opts.ServiceNamespace, 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 5d167ced..9d429436 100644 --- a/api/server/options.go +++ b/api/server/options.go @@ -10,14 +10,15 @@ import ( type Option func(o *Options) type Options struct { - EnableACME bool - EnableCORS bool - ACMEProvider acme.Provider - EnableTLS bool - ACMEHosts []string - TLSConfig *tls.Config - Namespace string - Resolver resolver.Resolver + EnableACME bool + EnableCORS bool + ACMEProvider acme.Provider + EnableTLS bool + ACMEHosts []string + TLSConfig *tls.Config + Resolver resolver.Resolver + Namespace string + ServiceNamespace string } func EnableCORS(b bool) Option { @@ -56,6 +57,12 @@ func TLSConfig(t *tls.Config) Option { } } +func ServiceNamespace(n string) Option { + return func(o *Options) { + o.ServiceNamespace = n + } +} + func Namespace(n string) Option { return func(o *Options) { o.Namespace = n From 11e1e9120a90d20d8d4349161a2b479e07a2686a Mon Sep 17 00:00:00 2001 From: Ben Toogood Date: Tue, 7 Apr 2020 10:10:37 +0100 Subject: [PATCH 02/15] Remove debugging --- api/server/auth/auth.go | 1 - 1 file changed, 1 deletion(-) diff --git a/api/server/auth/auth.go b/api/server/auth/auth.go index 5837dfa8..ab93443f 100644 --- a/api/server/auth/auth.go +++ b/api/server/auth/auth.go @@ -40,7 +40,6 @@ type authHandler struct { func (h authHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { // Determine the namespace and set it in the header namespace := h.namespaceFromRequest(req) - fmt.Printf("Namespace is %v\n", namespace) req.Header.Set(auth.NamespaceKey, namespace) // Extract the token from the request From 501fc5c05907ed876d66901983026d20b87a47c3 Mon Sep 17 00:00:00 2001 From: Ben Toogood Date: Tue, 7 Apr 2020 10:28:39 +0100 Subject: [PATCH 03/15] Refactor to use publicsuffix --- api/server/auth/auth.go | 34 ++++++++++++++++++++++------------ api/server/auth/auth_test.go | 5 ++++- go.sum | 1 + 3 files changed, 27 insertions(+), 13 deletions(-) diff --git a/api/server/auth/auth.go b/api/server/auth/auth.go index ab93443f..eb064421 100644 --- a/api/server/auth/auth.go +++ b/api/server/auth/auth.go @@ -12,6 +12,7 @@ import ( "github.com/micro/go-micro/v2/api/resolver/path" "github.com/micro/go-micro/v2/auth" "github.com/micro/go-micro/v2/logger" + "golang.org/x/net/publicsuffix" ) // CombinedAuthHandler wraps a server and authenticates requests @@ -39,7 +40,7 @@ type authHandler struct { func (h authHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { // Determine the namespace and set it in the header - namespace := h.namespaceFromRequest(req) + namespace := h.NamespaceFromRequest(req) req.Header.Set(auth.NamespaceKey, namespace) // Extract the token from the request @@ -131,7 +132,7 @@ func (h authHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { http.Redirect(w, req, loginWithRedirect, http.StatusTemporaryRedirect) } -func (h authHandler) namespaceFromRequest(req *http.Request) string { +func (h authHandler) NamespaceFromRequest(req *http.Request) string { // check to see what the provided namespace is, we only do // domain mapping if the namespace is set to 'domain' if h.namespace != "domain" { @@ -161,18 +162,27 @@ func (h authHandler) namespaceFromRequest(req *http.Request) string { return auth.DefaultNamespace } - // TODO: this logic needs to be replaced with usage of publicsuffix - // if host is not a subdomain, deturn default namespace - comps := strings.Split(host, ".") - if len(comps) < 3 { + // extract the top level domain plus one (e.g. 'myapp.com') + domain, err := publicsuffix.EffectiveTLDPlusOne(host) + if err != nil { + logger.Debugf("Unable to extract domain from %v", host) return auth.DefaultNamespace } - // return the reversed subdomain as the namespace - nComps := comps[0 : len(comps)-2] - for i := len(nComps)/2 - 1; i >= 0; i-- { - opp := len(nComps) - 1 - i - nComps[i], nComps[opp] = nComps[opp], nComps[i] + // check to see if the domain is the host, in this + // case we return the default namespace + if domain == host { + return auth.DefaultNamespace } - return strings.Join(nComps, ".") + + // remove the domain from the host, leaving the subdomain + subdomain := strings.TrimSuffix(host, "."+domain) + + // return the reversed subdomain as the namespace + comps := strings.Split(subdomain, ".") + for i := len(comps)/2 - 1; i >= 0; i-- { + opp := len(comps) - 1 - i + comps[i], comps[opp] = comps[opp], comps[i] + } + return strings.Join(comps, ".") } diff --git a/api/server/auth/auth_test.go b/api/server/auth/auth_test.go index cf454e28..04923e35 100644 --- a/api/server/auth/auth_test.go +++ b/api/server/auth/auth_test.go @@ -13,6 +13,7 @@ func TestNamespaceFromRequest(t *testing.T) { Namespace string }{ {Host: "micro.mu", Namespace: auth.DefaultNamespace}, + {Host: "micro.com.au", Namespace: auth.DefaultNamespace}, {Host: "web.micro.mu", Namespace: auth.DefaultNamespace}, {Host: "api.micro.mu", Namespace: auth.DefaultNamespace}, {Host: "myapp.com", Namespace: auth.DefaultNamespace}, @@ -23,9 +24,11 @@ func TestNamespaceFromRequest(t *testing.T) { {Host: "81.151.101.146", Namespace: auth.DefaultNamespace}, } + h := &authHandler{namespace: "domain"} + for _, tc := range tt { t.Run(tc.Host, func(t *testing.T) { - ns := namespaceFromRequest(&http.Request{Host: tc.Host}) + ns := h.NamespaceFromRequest(&http.Request{Host: tc.Host}) if ns != tc.Namespace { t.Errorf("Expected namespace %v for host %v, actually got %v", tc.Namespace, tc.Host, ns) } diff --git a/go.sum b/go.sum index 7d8a1ee1..b7d9c90b 100644 --- a/go.sum +++ b/go.sum @@ -461,6 +461,7 @@ golang.org/x/net v0.0.0-20190930134127-c5a3c61f89f3/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20191027093000-83d349e8ac1a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200222125558-5a598a2470a0 h1:MsuvTghUPjX762sGLnGsxC3HM0B5r83wEtYcYR8/vRs= golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= From bd23dc1f18fd0a660bc96e9012a0adfbcccf3c4a Mon Sep 17 00:00:00 2001 From: Ben Toogood Date: Tue, 7 Apr 2020 10:34:26 +0100 Subject: [PATCH 04/15] Improve micro.mu check --- api/server/auth/auth.go | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/api/server/auth/auth.go b/api/server/auth/auth.go index eb064421..84bebb49 100644 --- a/api/server/auth/auth.go +++ b/api/server/auth/auth.go @@ -147,11 +147,6 @@ func (h authHandler) NamespaceFromRequest(req *http.Request) string { host = req.Host // host does not contain a port } - // check for the micro.mu domain - if strings.HasSuffix(host, "micro.mu") { - return auth.DefaultNamespace - } - // check for an ip address if net.ParseIP(host) != nil { return auth.DefaultNamespace @@ -169,9 +164,9 @@ func (h authHandler) NamespaceFromRequest(req *http.Request) string { return auth.DefaultNamespace } - // check to see if the domain is the host, in this - // case we return the default namespace - if domain == host { + // check to see if the domain matches the host of micr.mu, in + // these casees we return the default namespace + if domain == host || domain == "micro.mu" { return auth.DefaultNamespace } From 316424f0f7c7f0bac8f68447550518a54e130226 Mon Sep 17 00:00:00 2001 From: Ben Toogood Date: Tue, 7 Apr 2020 10:35:57 +0100 Subject: [PATCH 05/15] Fix comments typo --- api/server/auth/auth.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/server/auth/auth.go b/api/server/auth/auth.go index 84bebb49..ad6a8c87 100644 --- a/api/server/auth/auth.go +++ b/api/server/auth/auth.go @@ -164,8 +164,8 @@ func (h authHandler) NamespaceFromRequest(req *http.Request) string { return auth.DefaultNamespace } - // check to see if the domain matches the host of micr.mu, in - // these casees we return the default namespace + // check to see if the domain matches the host of micro.mu, in + // these cases we return the default namespace if domain == host || domain == "micro.mu" { return auth.DefaultNamespace } From 9e116731b10f5c21b6cd729d7606e791d8f144e3 Mon Sep 17 00:00:00 2001 From: Ben Toogood Date: Tue, 7 Apr 2020 10:38:27 +0100 Subject: [PATCH 06/15] ServiceNamespace => ServicePrefix in api server --- api/server/options.go | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/api/server/options.go b/api/server/options.go index 9d429436..6fd61672 100644 --- a/api/server/options.go +++ b/api/server/options.go @@ -10,15 +10,15 @@ 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 - Namespace string - ServiceNamespace string + EnableACME bool + EnableCORS bool + ACMEProvider acme.Provider + EnableTLS bool + ACMEHosts []string + TLSConfig *tls.Config + Resolver resolver.Resolver + Namespace string + ServicePrefix string } func EnableCORS(b bool) Option { @@ -57,9 +57,9 @@ func TLSConfig(t *tls.Config) Option { } } -func ServiceNamespace(n string) Option { +func ServicePrefix(n string) Option { return func(o *Options) { - o.ServiceNamespace = n + o.ServicePrefix = n } } From 977934f8fd8ecfa4984ecb3f8097f6a8a1c453da Mon Sep 17 00:00:00 2001 From: Ben Toogood Date: Tue, 7 Apr 2020 10:39:27 +0100 Subject: [PATCH 07/15] ServiceNamespace => ServicePrefix in api server --- api/server/http/http.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/server/http/http.go b/api/server/http/http.go index 02238aa9..3d1030ac 100644 --- a/api/server/http/http.go +++ b/api/server/http/http.go @@ -53,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(s.opts.ServiceNamespace, s.opts.Namespace, s.opts.Resolver, handler) + h = auth.CombinedAuthHandler(s.opts.ServicePrefix, s.opts.Namespace, s.opts.Resolver, handler) if s.opts.EnableCORS { h = cors.CombinedCORSHandler(h) From 76f6f8031847c8c373bb2df0ac217834a4acbede Mon Sep 17 00:00:00 2001 From: Ben Toogood Date: Tue, 7 Apr 2020 11:23:21 +0100 Subject: [PATCH 08/15] Default to Hostname --- api/server/auth/auth.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/server/auth/auth.go b/api/server/auth/auth.go index 43c3a888..0faf1447 100644 --- a/api/server/auth/auth.go +++ b/api/server/auth/auth.go @@ -140,7 +140,7 @@ func (h authHandler) NamespaceFromRequest(req *http.Request) string { } // determine the host, e.g. dev.micro.mu:8080 - var host string + host := req.URL.Hostname() if h, _, err := net.SplitHostPort(req.Host); err == nil { host = h // host does contain a port } else if strings.Contains(err.Error(), "missing port in address") { From 05ac3ff274c539e0ab918dc05df0604c322f0fec Mon Sep 17 00:00:00 2001 From: Ben Toogood Date: Tue, 7 Apr 2020 11:24:13 +0100 Subject: [PATCH 09/15] Tweak --- api/server/auth/auth.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/api/server/auth/auth.go b/api/server/auth/auth.go index 0faf1447..7ae99214 100644 --- a/api/server/auth/auth.go +++ b/api/server/auth/auth.go @@ -141,10 +141,12 @@ func (h authHandler) NamespaceFromRequest(req *http.Request) string { // determine the host, e.g. dev.micro.mu:8080 host := req.URL.Hostname() - if h, _, err := net.SplitHostPort(req.Host); err == nil { - host = h // host does contain a port - } else if strings.Contains(err.Error(), "missing port in address") { - host = req.Host // host does not contain a port + if len(host) == 0 { + if h, _, err := net.SplitHostPort(req.Host); err == nil { + host = h // host does contain a port + } else if strings.Contains(err.Error(), "missing port in address") { + host = req.Host // host does not contain a port + } } // check for an ip address From 9d598836c307bfd4bcf7ba69c7e64e0b0667570b Mon Sep 17 00:00:00 2001 From: Ben Toogood Date: Tue, 7 Apr 2020 11:37:04 +0100 Subject: [PATCH 10/15] Fix Tests --- api/server/auth/auth_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/api/server/auth/auth_test.go b/api/server/auth/auth_test.go index 04923e35..7fd09581 100644 --- a/api/server/auth/auth_test.go +++ b/api/server/auth/auth_test.go @@ -2,6 +2,7 @@ package auth import ( "net/http" + "net/url" "testing" "github.com/micro/go-micro/v2/auth" @@ -28,7 +29,7 @@ func TestNamespaceFromRequest(t *testing.T) { for _, tc := range tt { t.Run(tc.Host, func(t *testing.T) { - ns := h.NamespaceFromRequest(&http.Request{Host: tc.Host}) + ns := h.NamespaceFromRequest(&http.Request{Host: tc.Host, URL: &url.URL{Host: tc.Host}}) if ns != tc.Namespace { t.Errorf("Expected namespace %v for host %v, actually got %v", tc.Namespace, tc.Host, ns) } From 3df87510a150621ec8b9168774410a9521aa1c60 Mon Sep 17 00:00:00 2001 From: Ben Toogood Date: Tue, 7 Apr 2020 12:46:44 +0100 Subject: [PATCH 11/15] Add namespace --- auth/auth.go | 2 +- auth/options.go | 9 +++++++++ config/cmd/cmd.go | 9 +++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/auth/auth.go b/auth/auth.go index 5f954776..e34051fa 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -89,7 +89,7 @@ type Token struct { const ( // DefaultNamespace used for auth - DefaultNamespace = "micro" + DefaultNamespace = "go.micro" // NamespaceKey is the key used when storing the namespace in metadata NamespaceKey = "Micro-Namespace" // MetadataKey is the key used when storing the account in metadata diff --git a/auth/options.go b/auth/options.go index 929cf674..d3c0f4ea 100644 --- a/auth/options.go +++ b/auth/options.go @@ -8,6 +8,8 @@ import ( ) type Options struct { + // Namespace the service belongs to + Namespace string // ID is the services auth ID ID string // Secret is used to authenticate the service @@ -28,6 +30,13 @@ type Options struct { type Option func(o *Options) +// Namespace the service belongs to +func Namespace(n string) Option { + return func(o *Options) { + o.Namespace = n + } +} + // Store to back auth func Store(s store.Store) Option { return func(o *Options) { diff --git a/config/cmd/cmd.go b/config/cmd/cmd.go index 34e417dd..3eb6fb5e 100644 --- a/config/cmd/cmd.go +++ b/config/cmd/cmd.go @@ -314,6 +314,12 @@ var ( EnvVars: []string{"MICRO_CONFIG"}, Usage: "The source of the config to be used to get configuration", }, + &cli.StringFlag{ + Name: "namespace", + EnvVars: []string{"MICRO_NAMESPACE"}, + Usage: "The namespace the service belongs to", + Value: auth.DefaultNamespace, + }, } DefaultBrokers = map[string]func(...broker.Option) broker.Broker{ @@ -678,6 +684,9 @@ func (c *cmd) Before(ctx *cli.Context) error { ctx.String("auth_id"), ctx.String("auth_secret"), )) } + if len(ctx.String("namespace")) > 0 { + authOpts = append(authOpts, auth.Namespace(ctx.String("namespace"))) + } if len(ctx.String("auth_public_key")) > 0 { authOpts = append(authOpts, auth.PublicKey(ctx.String("auth_public_key"))) From 4362a885eb824cc583b0d05cb0dd1c884ea0c19a Mon Sep 17 00:00:00 2001 From: Ben Toogood Date: Tue, 7 Apr 2020 16:24:51 +0100 Subject: [PATCH 12/15] Refactor Namespace Resolver --- api/resolver/resolver.go | 8 +++ api/server/auth/auth.go | 96 ++++++------------------------------ api/server/auth/auth_test.go | 38 -------------- api/server/http/http.go | 2 +- api/server/options.go | 35 ++++++------- auth/service/service.go | 12 +++-- go.sum | 4 ++ util/wrapper/wrapper.go | 30 ++--------- 8 files changed, 53 insertions(+), 172 deletions(-) delete mode 100644 api/server/auth/auth_test.go diff --git a/api/resolver/resolver.go b/api/resolver/resolver.go index 12854b19..daa7078d 100644 --- a/api/resolver/resolver.go +++ b/api/resolver/resolver.go @@ -11,6 +11,12 @@ 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) @@ -27,6 +33,8 @@ type Endpoint struct { Method string // HTTP Path e.g /greeter. Path string + // Namespace, g.g. go.micro + Namespace string } type Options struct { diff --git a/api/server/auth/auth.go b/api/server/auth/auth.go index 7ae99214..deffa3dc 100644 --- a/api/server/auth/auth.go +++ b/api/server/auth/auth.go @@ -3,44 +3,35 @@ package auth import ( "context" "fmt" - "net" "net/http" "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" - "golang.org/x/net/publicsuffix" ) // CombinedAuthHandler wraps a server and authenticates requests -func CombinedAuthHandler(prefix, namespace string, r resolver.Resolver, h http.Handler) http.Handler { - if r == nil { - r = path.NewResolver() - } - +func CombinedAuthHandler(r resolver.Resolver, nr resolver.NamespaceResolver, h http.Handler) http.Handler { return authHandler{ - handler: h, - resolver: r, - auth: auth.DefaultAuth, - servicePrefix: prefix, - namespace: namespace, + handler: h, + resolver: r, + nsResolver: nr, + auth: auth.DefaultAuth, } } type authHandler struct { - handler http.Handler - auth auth.Auth - resolver resolver.Resolver - namespace string - servicePrefix string + 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.NamespaceFromRequest(req) + namespace := h.nsResolver.Resolve(req) req.Header.Set(auth.NamespaceKey, namespace) // Extract the token from the request @@ -63,14 +54,7 @@ func (h authHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { // account doesn't necesserially mean a forbidden request acc, err := h.auth.Inspect(token) if err != nil { - acc = &auth.Account{Namespace: namespace} - } - - // Check the accounts namespace matches the namespace we're operating - // within. If not forbid the request and log the occurance. - if acc.Namespace != namespace { - logger.Debugf("Cross namespace request warning: account %v (%v) requested access to %v in the %v namespace", acc.ID, acc.Namespace, req.URL.Path, namespace) - // http.Error(w, "Forbidden namespace", 403) + acc = &auth.Account{} } // Determine the name of the service being requested @@ -90,9 +74,9 @@ func (h authHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { } // construct the resource name, e.g. home => go.micro.web.home - resName := h.servicePrefix + resName := namespace if len(endpoint.Name) > 0 { - resName = resName + "." + endpoint.Name + resName = namespace + "." + endpoint.Name } // determine the resource path. there is an inconsistency in how resolvers @@ -127,59 +111,7 @@ func (h authHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { } // Redirect to the login path - params := url.Values{"redirect_to": {req.URL.Path}} + params := url.Values{"redirect_to": {req.URL.String()}} loginWithRedirect := fmt.Sprintf("%v?%v", loginURL, params.Encode()) http.Redirect(w, req, loginWithRedirect, http.StatusTemporaryRedirect) } - -func (h authHandler) NamespaceFromRequest(req *http.Request) string { - // check to see what the provided namespace is, we only do - // domain mapping if the namespace is set to 'domain' - if h.namespace != "domain" { - return h.namespace - } - - // determine the host, e.g. dev.micro.mu:8080 - host := req.URL.Hostname() - if len(host) == 0 { - if h, _, err := net.SplitHostPort(req.Host); err == nil { - host = h // host does contain a port - } else if strings.Contains(err.Error(), "missing port in address") { - host = req.Host // host does not contain a port - } - } - - // check for an ip address - if net.ParseIP(host) != nil { - return auth.DefaultNamespace - } - - // check for dev enviroment - if host == "localhost" || host == "127.0.0.1" { - return auth.DefaultNamespace - } - - // extract the top level domain plus one (e.g. 'myapp.com') - domain, err := publicsuffix.EffectiveTLDPlusOne(host) - if err != nil { - logger.Debugf("Unable to extract domain from %v", host) - return auth.DefaultNamespace - } - - // check to see if the domain matches the host of micro.mu, in - // these cases we return the default namespace - if domain == host || domain == "micro.mu" { - return auth.DefaultNamespace - } - - // remove the domain from the host, leaving the subdomain - subdomain := strings.TrimSuffix(host, "."+domain) - - // return the reversed subdomain as the namespace - comps := strings.Split(subdomain, ".") - for i := len(comps)/2 - 1; i >= 0; i-- { - opp := len(comps) - 1 - i - comps[i], comps[opp] = comps[opp], comps[i] - } - return strings.Join(comps, ".") -} diff --git a/api/server/auth/auth_test.go b/api/server/auth/auth_test.go deleted file mode 100644 index 7fd09581..00000000 --- a/api/server/auth/auth_test.go +++ /dev/null @@ -1,38 +0,0 @@ -package auth - -import ( - "net/http" - "net/url" - "testing" - - "github.com/micro/go-micro/v2/auth" -) - -func TestNamespaceFromRequest(t *testing.T) { - tt := []struct { - Host string - Namespace string - }{ - {Host: "micro.mu", Namespace: auth.DefaultNamespace}, - {Host: "micro.com.au", Namespace: auth.DefaultNamespace}, - {Host: "web.micro.mu", Namespace: auth.DefaultNamespace}, - {Host: "api.micro.mu", Namespace: auth.DefaultNamespace}, - {Host: "myapp.com", Namespace: auth.DefaultNamespace}, - {Host: "staging.myapp.com", Namespace: "staging"}, - {Host: "staging.myapp.m3o.app", Namespace: "myapp.staging"}, - {Host: "127.0.0.1", Namespace: auth.DefaultNamespace}, - {Host: "localhost", Namespace: auth.DefaultNamespace}, - {Host: "81.151.101.146", Namespace: auth.DefaultNamespace}, - } - - h := &authHandler{namespace: "domain"} - - for _, tc := range tt { - t.Run(tc.Host, func(t *testing.T) { - ns := h.NamespaceFromRequest(&http.Request{Host: tc.Host, URL: &url.URL{Host: tc.Host}}) - if ns != tc.Namespace { - t.Errorf("Expected namespace %v for host %v, actually got %v", tc.Namespace, tc.Host, ns) - } - }) - } -} diff --git a/api/server/http/http.go b/api/server/http/http.go index 3d1030ac..c35d24b7 100644 --- a/api/server/http/http.go +++ b/api/server/http/http.go @@ -53,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(s.opts.ServicePrefix, s.opts.Namespace, s.opts.Resolver, handler) + h = auth.CombinedAuthHandler(s.opts.Resolver, s.opts.NamespaceResolver, handler) if s.opts.EnableCORS { h = cors.CombinedCORSHandler(h) diff --git a/api/server/options.go b/api/server/options.go index 6fd61672..9aa14579 100644 --- a/api/server/options.go +++ b/api/server/options.go @@ -10,15 +10,14 @@ 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 - Namespace string - ServicePrefix string + EnableACME bool + EnableCORS bool + ACMEProvider acme.Provider + EnableTLS bool + ACMEHosts []string + TLSConfig *tls.Config + Resolver resolver.Resolver + NamespaceResolver resolver.NamespaceResolver } func EnableCORS(b bool) Option { @@ -57,20 +56,14 @@ func TLSConfig(t *tls.Config) Option { } } -func ServicePrefix(n string) Option { - return func(o *Options) { - o.ServicePrefix = n - } -} - -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 } } + +func NamespaceResolver(r resolver.NamespaceResolver) Option { + return func(o *Options) { + o.NamespaceResolver = r + } +} diff --git a/auth/service/service.go b/auth/service/service.go index 57d6082e..cb1740c7 100644 --- a/auth/service/service.go +++ b/auth/service/service.go @@ -158,6 +158,11 @@ func (s *svc) Revoke(role string, res *auth.Resource) error { // Verify an account has access to a resource func (s *svc) Verify(acc *auth.Account, res *auth.Resource) error { + // set the namespace on the resource + if len(res.Namespace) == 0 { + res.Namespace = s.Options().Namespace + } + queries := [][]string{ {res.Namespace, res.Type, res.Name, res.Endpoint}, // check for specific role, e.g. service.foo.ListFoo:admin (role is checked in accessForRule) {res.Namespace, res.Type, res.Name, "*"}, // check for wildcard endpoint, e.g. service.foo* @@ -205,16 +210,15 @@ func (s *svc) Verify(acc *auth.Account, res *auth.Resource) error { func (s *svc) Inspect(token string) (*auth.Account, error) { // try to decode JWT locally and fall back to srv if an error occurs if len(strings.Split(token, ".")) == 3 && s.jwt != nil { - if acc, err := s.jwt.Inspect(token); err == nil { - return acc, nil - } + return s.jwt.Inspect(token) } + // the token is not a JWT or we do not have the keys to decode it, + // fall back to the auth service rsp, err := s.auth.Inspect(context.TODO(), &pb.InspectRequest{Token: token}) if err != nil { return nil, err } - return serializeAccount(rsp.Account), nil } diff --git a/go.sum b/go.sum index b7d9c90b..4de561e2 100644 --- a/go.sum +++ b/go.sum @@ -399,6 +399,7 @@ go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/multierr v1.3.0 h1:sFPn2GLc3poCkfrpIXGhBD2X0CMIo4Q/zSULXrj/+uc= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/ratelimit v0.0.0-20180316092928-c15da0234277/go.mod h1:2X8KaoNd1J0lZV+PxJk/5+DGbO/tpwLR1m++a7FnB/Y= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.13.0 h1:nR6NoDBgAf67s68NhaXbsojM+2gxp3S1hWkHDl27pVU= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= @@ -432,6 +433,7 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f h1:J5lckAjkw6qYlOZNj90mLYNTEKDvWeuc1yieZ8qUzUE= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= @@ -529,6 +531,7 @@ golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361 h1:RIIXAeV6GvDBuADKumTODatUqANFZ+5BPMnzsy4hulY= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -600,6 +603,7 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/util/wrapper/wrapper.go b/util/wrapper/wrapper.go index 100f2f2b..9a7fe54f 100644 --- a/util/wrapper/wrapper.go +++ b/util/wrapper/wrapper.go @@ -9,7 +9,6 @@ import ( "github.com/micro/go-micro/v2/debug/stats" "github.com/micro/go-micro/v2/debug/trace" "github.com/micro/go-micro/v2/errors" - "github.com/micro/go-micro/v2/logger" "github.com/micro/go-micro/v2/metadata" "github.com/micro/go-micro/v2/server" ) @@ -155,11 +154,6 @@ func AuthHandler(fn func() auth.Auth) server.HandlerWrapper { return h(ctx, req, rsp) } - // Check for auth service endpoints which should be excluded from auth - if strings.HasPrefix(req.Endpoint(), "Auth.") { - return h(ctx, req, rsp) - } - // Extract the token if present. Note: if noop is being used // then the token can be blank without erroring var token string @@ -172,33 +166,17 @@ func AuthHandler(fn func() auth.Auth) server.HandlerWrapper { token = header[len(auth.BearerScheme):] } - // Get the namespace for the request - namespace, ok := metadata.Get(ctx, auth.NamespaceKey) - if !ok { - logger.Debugf("Missing request namespace") - namespace = auth.DefaultNamespace - } - // Inspect the token and get the account account, err := a.Inspect(token) if err != nil { - account = &auth.Account{Namespace: namespace} - } - - // Check the accounts namespace matches the namespace we're operating - // within. If not forbid the request and log the occurance. - if account.Namespace != namespace { - logger.Debugf("Cross namespace request forbidden: account %v (%v) requested access to %v %v in the %v namespace", - account.ID, account.Namespace, req.Service(), req.Endpoint(), namespace) - // return errors.Forbidden(req.Service(), "cross namespace request") + account = &auth.Account{} } // construct the resource res := &auth.Resource{ - Type: "service", - Name: req.Service(), - Endpoint: req.Endpoint(), - Namespace: namespace, + Type: "service", + Name: req.Service(), + Endpoint: req.Endpoint(), } // Verify the caller has access to the resource From 3735b0e52917ef7af6886cc3738a4eebde522d67 Mon Sep 17 00:00:00 2001 From: Ben Toogood Date: Tue, 7 Apr 2020 16:27:01 +0100 Subject: [PATCH 13/15] Remove global namespace option --- config/cmd/cmd.go | 9 --------- 1 file changed, 9 deletions(-) diff --git a/config/cmd/cmd.go b/config/cmd/cmd.go index 3eb6fb5e..34e417dd 100644 --- a/config/cmd/cmd.go +++ b/config/cmd/cmd.go @@ -314,12 +314,6 @@ var ( EnvVars: []string{"MICRO_CONFIG"}, Usage: "The source of the config to be used to get configuration", }, - &cli.StringFlag{ - Name: "namespace", - EnvVars: []string{"MICRO_NAMESPACE"}, - Usage: "The namespace the service belongs to", - Value: auth.DefaultNamespace, - }, } DefaultBrokers = map[string]func(...broker.Option) broker.Broker{ @@ -684,9 +678,6 @@ func (c *cmd) Before(ctx *cli.Context) error { ctx.String("auth_id"), ctx.String("auth_secret"), )) } - if len(ctx.String("namespace")) > 0 { - authOpts = append(authOpts, auth.Namespace(ctx.String("namespace"))) - } if len(ctx.String("auth_public_key")) > 0 { authOpts = append(authOpts, auth.PublicKey(ctx.String("auth_public_key"))) From 67cd59d7bcd9c42c0c6b11e51049c0bbb57f27c4 Mon Sep 17 00:00:00 2001 From: Ben Toogood Date: Tue, 7 Apr 2020 16:27:59 +0100 Subject: [PATCH 14/15] Rename namespace from Resolver.Endpoint --- api/resolver/resolver.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/api/resolver/resolver.go b/api/resolver/resolver.go index daa7078d..99a10802 100644 --- a/api/resolver/resolver.go +++ b/api/resolver/resolver.go @@ -33,8 +33,6 @@ type Endpoint struct { Method string // HTTP Path e.g /greeter. Path string - // Namespace, g.g. go.micro - Namespace string } type Options struct { From e907d24e3bfafca5b53a0f7acb935c152d6016f7 Mon Sep 17 00:00:00 2001 From: Ben Toogood Date: Tue, 7 Apr 2020 19:29:26 +0100 Subject: [PATCH 15/15] 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 - } -}