Browse code

Move middleware to interfaces.

This makes separating middlewares from the core api easier.
As an example, the authorization middleware is moved to
it's own package.

Initialize all static middlewares when the server is created, reducing
allocations every time a route is wrapper with the middlewares.

Signed-off-by: David Calavera <david.calavera@gmail.com>

David Calavera authored on 2016/04/09 08:22:39
Showing 12 changed files
... ...
@@ -2,10 +2,8 @@ package server
2 2
 
3 3
 import (
4 4
 	"github.com/Sirupsen/logrus"
5
-	"github.com/docker/docker/api"
6 5
 	"github.com/docker/docker/api/server/httputils"
7 6
 	"github.com/docker/docker/api/server/middleware"
8
-	"github.com/docker/docker/pkg/authorization"
9 7
 )
10 8
 
11 9
 // handleWithGlobalMiddlwares wraps the handler function for a request with
... ...
@@ -14,27 +12,13 @@ import (
14 14
 func (s *Server) handleWithGlobalMiddlewares(handler httputils.APIFunc) httputils.APIFunc {
15 15
 	next := handler
16 16
 
17
-	handleVersion := middleware.NewVersionMiddleware(s.cfg.Version, api.DefaultVersion, api.MinVersion)
18
-	next = handleVersion(next)
19
-
20
-	if s.cfg.EnableCors {
21
-		handleCORS := middleware.NewCORSMiddleware(s.cfg.CorsHeaders)
22
-		next = handleCORS(next)
17
+	for _, m := range s.middlewares {
18
+		next = m.WrapHandler(next)
23 19
 	}
24 20
 
25
-	handleUserAgent := middleware.NewUserAgentMiddleware(s.cfg.Version)
26
-	next = handleUserAgent(next)
27
-
28
-	// Only want this on debug level
29 21
 	if s.cfg.Logging && logrus.GetLevel() == logrus.DebugLevel {
30 22
 		next = middleware.DebugRequestMiddleware(next)
31 23
 	}
32 24
 
33
-	if len(s.cfg.AuthorizationPluginNames) > 0 {
34
-		s.authZPlugins = authorization.NewPlugins(s.cfg.AuthorizationPluginNames)
35
-		handleAuthorization := middleware.NewAuthorizationMiddleware(s.authZPlugins)
36
-		next = handleAuthorization(next)
37
-	}
38
-
39 25
 	return next
40 26
 }
41 27
deleted file mode 100644
... ...
@@ -1,50 +0,0 @@
1
-package middleware
2
-
3
-import (
4
-	"net/http"
5
-
6
-	"github.com/Sirupsen/logrus"
7
-	"github.com/docker/docker/api/server/httputils"
8
-	"github.com/docker/docker/pkg/authorization"
9
-	"golang.org/x/net/context"
10
-)
11
-
12
-// NewAuthorizationMiddleware creates a new Authorization middleware.
13
-func NewAuthorizationMiddleware(plugins []authorization.Plugin) Middleware {
14
-	return func(handler httputils.APIFunc) httputils.APIFunc {
15
-		return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
16
-
17
-			user := ""
18
-			userAuthNMethod := ""
19
-
20
-			// Default authorization using existing TLS connection credentials
21
-			// FIXME: Non trivial authorization mechanisms (such as advanced certificate validations, kerberos support
22
-			// and ldap) will be extracted using AuthN feature, which is tracked under:
23
-			// https://github.com/docker/docker/pull/20883
24
-			if r.TLS != nil && len(r.TLS.PeerCertificates) > 0 {
25
-				user = r.TLS.PeerCertificates[0].Subject.CommonName
26
-				userAuthNMethod = "TLS"
27
-			}
28
-
29
-			authCtx := authorization.NewCtx(plugins, user, userAuthNMethod, r.Method, r.RequestURI)
30
-
31
-			if err := authCtx.AuthZRequest(w, r); err != nil {
32
-				logrus.Errorf("AuthZRequest for %s %s returned error: %s", r.Method, r.RequestURI, err)
33
-				return err
34
-			}
35
-
36
-			rw := authorization.NewResponseModifier(w)
37
-
38
-			if err := handler(ctx, rw, r, vars); err != nil {
39
-				logrus.Errorf("Handler for %s %s returned error: %s", r.Method, r.RequestURI, err)
40
-				return err
41
-			}
42
-
43
-			if err := authCtx.AuthZResponse(rw, r); err != nil {
44
-				logrus.Errorf("AuthZResponse for %s %s returned error: %s", r.Method, r.RequestURI, err)
45
-				return err
46
-			}
47
-			return nil
48
-		}
49
-	}
50
-}
... ...
@@ -4,30 +4,34 @@ import (
4 4
 	"net/http"
5 5
 
6 6
 	"github.com/Sirupsen/logrus"
7
-	"github.com/docker/docker/api/server/httputils"
8 7
 	"golang.org/x/net/context"
9 8
 )
10 9
 
11
-// NewCORSMiddleware creates a new CORS middleware.
12
-func NewCORSMiddleware(defaultHeaders string) Middleware {
13
-	return func(handler httputils.APIFunc) httputils.APIFunc {
14
-		return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
15
-			// If "api-cors-header" is not given, but "api-enable-cors" is true, we set cors to "*"
16
-			// otherwise, all head values will be passed to HTTP handler
17
-			corsHeaders := defaultHeaders
18
-			if corsHeaders == "" {
19
-				corsHeaders = "*"
20
-			}
10
+// CORSMiddleware injects CORS headers to each request
11
+// when it's configured.
12
+type CORSMiddleware struct {
13
+	defaultHeaders string
14
+}
21 15
 
22
-			writeCorsHeaders(w, r, corsHeaders)
23
-			return handler(ctx, w, r, vars)
24
-		}
25
-	}
16
+// NewCORSMiddleware creates a new CORSMiddleware with default headers.
17
+func NewCORSMiddleware(d string) CORSMiddleware {
18
+	return CORSMiddleware{defaultHeaders: d}
26 19
 }
27 20
 
28
-func writeCorsHeaders(w http.ResponseWriter, r *http.Request, corsHeaders string) {
29
-	logrus.Debugf("CORS header is enabled and set to: %s", corsHeaders)
30
-	w.Header().Add("Access-Control-Allow-Origin", corsHeaders)
31
-	w.Header().Add("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, X-Registry-Auth")
32
-	w.Header().Add("Access-Control-Allow-Methods", "HEAD, GET, POST, DELETE, PUT, OPTIONS")
21
+// WrapHandler returns a new handler function wrapping the previous one in the request chain.
22
+func (c CORSMiddleware) WrapHandler(handler func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error) func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
23
+	return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
24
+		// If "api-cors-header" is not given, but "api-enable-cors" is true, we set cors to "*"
25
+		// otherwise, all head values will be passed to HTTP handler
26
+		corsHeaders := c.defaultHeaders
27
+		if corsHeaders == "" {
28
+			corsHeaders = "*"
29
+		}
30
+
31
+		logrus.Debugf("CORS header is enabled and set to: %s", corsHeaders)
32
+		w.Header().Add("Access-Control-Allow-Origin", corsHeaders)
33
+		w.Header().Add("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, X-Registry-Auth")
34
+		w.Header().Add("Access-Control-Allow-Methods", "HEAD, GET, POST, DELETE, PUT, OPTIONS")
35
+		return handler(ctx, w, r, vars)
36
+	}
33 37
 }
... ...
@@ -13,7 +13,7 @@ import (
13 13
 )
14 14
 
15 15
 // DebugRequestMiddleware dumps the request to logger
16
-func DebugRequestMiddleware(handler httputils.APIFunc) httputils.APIFunc {
16
+func DebugRequestMiddleware(handler func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error) func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
17 17
 	return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
18 18
 		logrus.Debugf("Calling %s %s", r.Method, r.RequestURI)
19 19
 
... ...
@@ -1,7 +1,13 @@
1 1
 package middleware
2 2
 
3
-import "github.com/docker/docker/api/server/httputils"
3
+import (
4
+	"net/http"
4 5
 
5
-// Middleware is an adapter to allow the use of ordinary functions as Docker API filters.
6
-// Any function that has the appropriate signature can be registered as a middleware.
7
-type Middleware func(handler httputils.APIFunc) httputils.APIFunc
6
+	"golang.org/x/net/context"
7
+)
8
+
9
+// Middleware is an interface to allow the use of ordinary functions as Docker API filters.
10
+// Any struct that has the appropriate signature can be registered as a middleware.
11
+type Middleware interface {
12
+	WrapHandler(func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error) func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error
13
+}
... ...
@@ -10,28 +10,38 @@ import (
10 10
 	"golang.org/x/net/context"
11 11
 )
12 12
 
13
-// NewUserAgentMiddleware creates a new UserAgent middleware.
14
-func NewUserAgentMiddleware(versionCheck string) Middleware {
15
-	serverVersion := version.Version(versionCheck)
16
-
17
-	return func(handler httputils.APIFunc) httputils.APIFunc {
18
-		return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
19
-			ctx = context.WithValue(ctx, httputils.UAStringKey, r.Header.Get("User-Agent"))
20
-
21
-			if strings.Contains(r.Header.Get("User-Agent"), "Docker-Client/") {
22
-				userAgent := strings.Split(r.Header.Get("User-Agent"), "/")
23
-
24
-				// v1.20 onwards includes the GOOS of the client after the version
25
-				// such as Docker/1.7.0 (linux)
26
-				if len(userAgent) == 2 && strings.Contains(userAgent[1], " ") {
27
-					userAgent[1] = strings.Split(userAgent[1], " ")[0]
28
-				}
29
-
30
-				if len(userAgent) == 2 && !serverVersion.Equal(version.Version(userAgent[1])) {
31
-					logrus.Debugf("Client and server don't have the same version (client: %s, server: %s)", userAgent[1], serverVersion)
32
-				}
13
+// UserAgentMiddleware is a middleware that
14
+// validates the client user-agent.
15
+type UserAgentMiddleware struct {
16
+	serverVersion version.Version
17
+}
18
+
19
+// NewUserAgentMiddleware creates a new UserAgentMiddleware
20
+// with the server version.
21
+func NewUserAgentMiddleware(s version.Version) UserAgentMiddleware {
22
+	return UserAgentMiddleware{
23
+		serverVersion: s,
24
+	}
25
+}
26
+
27
+// WrapHandler returns a new handler function wrapping the previous one in the request chain.
28
+func (u UserAgentMiddleware) WrapHandler(handler func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error) func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
29
+	return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
30
+		ctx = context.WithValue(ctx, httputils.UAStringKey, r.Header.Get("User-Agent"))
31
+
32
+		if strings.Contains(r.Header.Get("User-Agent"), "Docker-Client/") {
33
+			userAgent := strings.Split(r.Header.Get("User-Agent"), "/")
34
+
35
+			// v1.20 onwards includes the GOOS of the client after the version
36
+			// such as Docker/1.7.0 (linux)
37
+			if len(userAgent) == 2 && strings.Contains(userAgent[1], " ") {
38
+				userAgent[1] = strings.Split(userAgent[1], " ")[0]
39
+			}
40
+
41
+			if len(userAgent) == 2 && !u.serverVersion.Equal(version.Version(userAgent[1])) {
42
+				logrus.Debugf("Client and server don't have the same version (client: %s, server: %s)", userAgent[1], u.serverVersion)
33 43
 			}
34
-			return handler(ctx, w, r, vars)
35 44
 		}
45
+		return handler(ctx, w, r, vars)
36 46
 	}
37 47
 }
... ...
@@ -5,7 +5,6 @@ import (
5 5
 	"net/http"
6 6
 	"runtime"
7 7
 
8
-	"github.com/docker/docker/api/server/httputils"
9 8
 	"github.com/docker/docker/pkg/version"
10 9
 	"golang.org/x/net/context"
11 10
 )
... ...
@@ -18,28 +17,43 @@ func (badRequestError) HTTPErrorStatusCode() int {
18 18
 	return http.StatusBadRequest
19 19
 }
20 20
 
21
-// NewVersionMiddleware creates a new Version middleware.
22
-func NewVersionMiddleware(versionCheck string, defaultVersion, minVersion version.Version) Middleware {
23
-	serverVersion := version.Version(versionCheck)
24
-
25
-	return func(handler httputils.APIFunc) httputils.APIFunc {
26
-		return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
27
-			apiVersion := version.Version(vars["version"])
28
-			if apiVersion == "" {
29
-				apiVersion = defaultVersion
30
-			}
31
-
32
-			if apiVersion.GreaterThan(defaultVersion) {
33
-				return badRequestError{fmt.Errorf("client is newer than server (client API version: %s, server API version: %s)", apiVersion, defaultVersion)}
34
-			}
35
-			if apiVersion.LessThan(minVersion) {
36
-				return badRequestError{fmt.Errorf("client version %s is too old. Minimum supported API version is %s, please upgrade your client to a newer version", apiVersion, minVersion)}
37
-			}
38
-
39
-			header := fmt.Sprintf("Docker/%s (%s)", serverVersion, runtime.GOOS)
40
-			w.Header().Set("Server", header)
41
-			ctx = context.WithValue(ctx, httputils.APIVersionKey, apiVersion)
42
-			return handler(ctx, w, r, vars)
21
+// VersionMiddleware is a middleware that
22
+// validates the client and server versions.
23
+type VersionMiddleware struct {
24
+	serverVersion  version.Version
25
+	defaultVersion version.Version
26
+	minVersion     version.Version
27
+}
28
+
29
+// NewVersionMiddleware creates a new VersionMiddleware
30
+// with the default versions.
31
+func NewVersionMiddleware(s, d, m version.Version) VersionMiddleware {
32
+	return VersionMiddleware{
33
+		serverVersion:  s,
34
+		defaultVersion: d,
35
+		minVersion:     m,
36
+	}
37
+}
38
+
39
+// WrapHandler returns a new handler function wrapping the previous one in the request chain.
40
+func (v VersionMiddleware) WrapHandler(handler func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error) func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
41
+	return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
42
+		apiVersion := version.Version(vars["version"])
43
+		if apiVersion == "" {
44
+			apiVersion = v.defaultVersion
43 45
 		}
46
+
47
+		if apiVersion.GreaterThan(v.defaultVersion) {
48
+			return badRequestError{fmt.Errorf("client is newer than server (client API version: %s, server API version: %s)", apiVersion, v.defaultVersion)}
49
+		}
50
+		if apiVersion.LessThan(v.minVersion) {
51
+			return badRequestError{fmt.Errorf("client version %s is too old. Minimum supported API version is %s, please upgrade your client to a newer version", apiVersion, v.minVersion)}
52
+		}
53
+
54
+		header := fmt.Sprintf("Docker/%s (%s)", v.serverVersion, runtime.GOOS)
55
+		w.Header().Set("Server", header)
56
+		ctx = context.WithValue(ctx, "api-version", apiVersion)
57
+		return handler(ctx, w, r, vars)
44 58
 	}
59
+
45 60
 }
... ...
@@ -21,8 +21,8 @@ func TestVersionMiddleware(t *testing.T) {
21 21
 
22 22
 	defaultVersion := version.Version("1.10.0")
23 23
 	minVersion := version.Version("1.2.0")
24
-	m := NewVersionMiddleware(defaultVersion.String(), defaultVersion, minVersion)
25
-	h := m(handler)
24
+	m := NewVersionMiddleware(defaultVersion, defaultVersion, minVersion)
25
+	h := m.WrapHandler(handler)
26 26
 
27 27
 	req, _ := http.NewRequest("GET", "/containers/json", nil)
28 28
 	resp := httptest.NewRecorder()
... ...
@@ -42,8 +42,8 @@ func TestVersionMiddlewareWithErrors(t *testing.T) {
42 42
 
43 43
 	defaultVersion := version.Version("1.10.0")
44 44
 	minVersion := version.Version("1.2.0")
45
-	m := NewVersionMiddleware(defaultVersion.String(), defaultVersion, minVersion)
46
-	h := m(handler)
45
+	m := NewVersionMiddleware(defaultVersion, defaultVersion, minVersion)
46
+	h := m.WrapHandler(handler)
47 47
 
48 48
 	req, _ := http.NewRequest("GET", "/containers/json", nil)
49 49
 	resp := httptest.NewRecorder()
... ...
@@ -8,8 +8,8 @@ import (
8 8
 
9 9
 	"github.com/Sirupsen/logrus"
10 10
 	"github.com/docker/docker/api/server/httputils"
11
+	"github.com/docker/docker/api/server/middleware"
11 12
 	"github.com/docker/docker/api/server/router"
12
-	"github.com/docker/docker/pkg/authorization"
13 13
 	"github.com/gorilla/mux"
14 14
 	"golang.org/x/net/context"
15 15
 )
... ...
@@ -20,13 +20,12 @@ const versionMatcher = "/v{version:[0-9.]+}"
20 20
 
21 21
 // Config provides the configuration for the API server
22 22
 type Config struct {
23
-	Logging                  bool
24
-	EnableCors               bool
25
-	CorsHeaders              string
26
-	AuthorizationPluginNames []string
27
-	Version                  string
28
-	SocketGroup              string
29
-	TLSConfig                *tls.Config
23
+	Logging     bool
24
+	EnableCors  bool
25
+	CorsHeaders string
26
+	Version     string
27
+	SocketGroup string
28
+	TLSConfig   *tls.Config
30 29
 }
31 30
 
32 31
 // Server contains instance details for the server
... ...
@@ -34,8 +33,8 @@ type Server struct {
34 34
 	cfg           *Config
35 35
 	servers       []*HTTPServer
36 36
 	routers       []router.Router
37
-	authZPlugins  []authorization.Plugin
38 37
 	routerSwapper *routerSwapper
38
+	middlewares   []middleware.Middleware
39 39
 }
40 40
 
41 41
 // New returns a new instance of the server based on the specified configuration.
... ...
@@ -46,6 +45,12 @@ func New(cfg *Config) *Server {
46 46
 	}
47 47
 }
48 48
 
49
+// UseMiddleware appends a new middleware to the request chain.
50
+// This needs to be called before the API routes are configured.
51
+func (s *Server) UseMiddleware(m middleware.Middleware) {
52
+	s.middlewares = append(s.middlewares, m)
53
+}
54
+
49 55
 // Accept sets a listener the server accepts connections into.
50 56
 func (s *Server) Accept(addr string, listeners ...net.Listener) {
51 57
 	for _, listener := range listeners {
... ...
@@ -6,7 +6,10 @@ import (
6 6
 	"strings"
7 7
 	"testing"
8 8
 
9
+	"github.com/docker/docker/api"
9 10
 	"github.com/docker/docker/api/server/httputils"
11
+	"github.com/docker/docker/api/server/middleware"
12
+	"github.com/docker/docker/pkg/version"
10 13
 
11 14
 	"golang.org/x/net/context"
12 15
 )
... ...
@@ -19,6 +22,8 @@ func TestMiddlewares(t *testing.T) {
19 19
 		cfg: cfg,
20 20
 	}
21 21
 
22
+	srv.UseMiddleware(middleware.NewVersionMiddleware(version.Version("0.1omega2"), api.DefaultVersion, api.MinVersion))
23
+
22 24
 	req, _ := http.NewRequest("GET", "/containers/json", nil)
23 25
 	resp := httptest.NewRecorder()
24 26
 	ctx := context.Background()
... ...
@@ -14,7 +14,9 @@ import (
14 14
 
15 15
 	"github.com/Sirupsen/logrus"
16 16
 	"github.com/docker/distribution/uuid"
17
+	"github.com/docker/docker/api"
17 18
 	apiserver "github.com/docker/docker/api/server"
19
+	"github.com/docker/docker/api/server/middleware"
18 20
 	"github.com/docker/docker/api/server/router"
19 21
 	"github.com/docker/docker/api/server/router/build"
20 22
 	"github.com/docker/docker/api/server/router/container"
... ...
@@ -29,12 +31,14 @@ import (
29 29
 	"github.com/docker/docker/dockerversion"
30 30
 	"github.com/docker/docker/libcontainerd"
31 31
 	"github.com/docker/docker/opts"
32
+	"github.com/docker/docker/pkg/authorization"
32 33
 	"github.com/docker/docker/pkg/jsonlog"
33 34
 	"github.com/docker/docker/pkg/listeners"
34 35
 	flag "github.com/docker/docker/pkg/mflag"
35 36
 	"github.com/docker/docker/pkg/pidfile"
36 37
 	"github.com/docker/docker/pkg/signal"
37 38
 	"github.com/docker/docker/pkg/system"
39
+	"github.com/docker/docker/pkg/version"
38 40
 	"github.com/docker/docker/registry"
39 41
 	"github.com/docker/docker/runconfig"
40 42
 	"github.com/docker/docker/utils"
... ...
@@ -208,10 +212,9 @@ func (cli *DaemonCli) CmdDaemon(args ...string) error {
208 208
 	}
209 209
 
210 210
 	serverConfig := &apiserver.Config{
211
-		AuthorizationPluginNames: cli.Config.AuthorizationPlugins,
212
-		Logging:                  true,
213
-		SocketGroup:              cli.Config.SocketGroup,
214
-		Version:                  dockerversion.Version,
211
+		Logging:     true,
212
+		SocketGroup: cli.Config.SocketGroup,
213
+		Version:     dockerversion.Version,
215 214
 	}
216 215
 	serverConfig = setPlatformServerConfig(serverConfig, cli.Config)
217 216
 
... ...
@@ -288,6 +291,7 @@ func (cli *DaemonCli) CmdDaemon(args ...string) error {
288 288
 		"graphdriver": d.GraphDriverName(),
289 289
 	}).Info("Docker daemon")
290 290
 
291
+	cli.initMiddlewares(api, serverConfig)
291 292
 	initRouter(api, d)
292 293
 
293 294
 	reload := func(config *daemon.Config) {
... ...
@@ -420,3 +424,24 @@ func initRouter(s *apiserver.Server, d *daemon.Daemon) {
420 420
 
421 421
 	s.InitRouter(utils.IsDebugEnabled(), routers...)
422 422
 }
423
+
424
+func (cli *DaemonCli) initMiddlewares(s *apiserver.Server, cfg *apiserver.Config) {
425
+	v := version.Version(cfg.Version)
426
+
427
+	vm := middleware.NewVersionMiddleware(v, api.DefaultVersion, api.MinVersion)
428
+	s.UseMiddleware(vm)
429
+
430
+	if cfg.EnableCors {
431
+		c := middleware.NewCORSMiddleware(cfg.CorsHeaders)
432
+		s.UseMiddleware(c)
433
+	}
434
+
435
+	u := middleware.NewUserAgentMiddleware(v)
436
+	s.UseMiddleware(u)
437
+
438
+	if len(cli.Config.AuthorizationPlugins) > 0 {
439
+		authZPlugins := authorization.NewPlugins(cli.Config.AuthorizationPlugins)
440
+		handleAuthorization := authorization.NewMiddleware(authZPlugins)
441
+		s.UseMiddleware(handleAuthorization)
442
+	}
443
+}
423 444
new file mode 100644
... ...
@@ -0,0 +1,60 @@
0
+package authorization
1
+
2
+import (
3
+	"net/http"
4
+
5
+	"github.com/Sirupsen/logrus"
6
+	"golang.org/x/net/context"
7
+)
8
+
9
+// Middleware uses a list of plugins to
10
+// handle authorization in the API requests.
11
+type Middleware struct {
12
+	plugins []Plugin
13
+}
14
+
15
+// NewMiddleware creates a new Middleware
16
+// with a slice of plugins.
17
+func NewMiddleware(p []Plugin) Middleware {
18
+	return Middleware{
19
+		plugins: p,
20
+	}
21
+}
22
+
23
+// WrapHandler returns a new handler function wrapping the previous one in the request chain.
24
+func (m Middleware) WrapHandler(handler func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error) func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
25
+	return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
26
+
27
+		user := ""
28
+		userAuthNMethod := ""
29
+
30
+		// Default authorization using existing TLS connection credentials
31
+		// FIXME: Non trivial authorization mechanisms (such as advanced certificate validations, kerberos support
32
+		// and ldap) will be extracted using AuthN feature, which is tracked under:
33
+		// https://github.com/docker/docker/pull/20883
34
+		if r.TLS != nil && len(r.TLS.PeerCertificates) > 0 {
35
+			user = r.TLS.PeerCertificates[0].Subject.CommonName
36
+			userAuthNMethod = "TLS"
37
+		}
38
+
39
+		authCtx := NewCtx(m.plugins, user, userAuthNMethod, r.Method, r.RequestURI)
40
+
41
+		if err := authCtx.AuthZRequest(w, r); err != nil {
42
+			logrus.Errorf("AuthZRequest for %s %s returned error: %s", r.Method, r.RequestURI, err)
43
+			return err
44
+		}
45
+
46
+		rw := NewResponseModifier(w)
47
+
48
+		if err := handler(ctx, rw, r, vars); err != nil {
49
+			logrus.Errorf("Handler for %s %s returned error: %s", r.Method, r.RequestURI, err)
50
+			return err
51
+		}
52
+
53
+		if err := authCtx.AuthZResponse(rw, r); err != nil {
54
+			logrus.Errorf("AuthZResponse for %s %s returned error: %s", r.Method, r.RequestURI, err)
55
+			return err
56
+		}
57
+		return nil
58
+	}
59
+}