From fe9654c5bf816288f794f2576ab7e61e83c291b4 Mon Sep 17 00:00:00 2001
From: Rohith <gambol99@gmail.com>
Date: Wed, 14 Sep 2016 23:45:03 +0100
Subject: [PATCH] - adding the ability to choose proxying the Authorization
 header (#133)

---
 CHANGELOG.md       | 41 ++++++++++++++++++++++---------------
 cli.go             |  7 +++++++
 config.go          | 21 ++++++++++---------
 doc.go             |  5 ++++-
 middleware.go      |  6 +++++-
 middleware_test.go | 51 ++++++++++++++++++++++++++++++++++++++++++++++
 server.go          |  2 +-
 server_test.go     | 31 +++++++++++++++-------------
 8 files changed, 121 insertions(+), 43 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8b82eae..45aeeba 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,13 @@
 
+#### **1.2.7**
+
+FIXES:
+ * Added unit tests for the logout handlers
+ * Added unit tests for the authorization header handling
+
+FEATURES:
+ * Allow the user to enable or disable adding the Authorization header
+
 #### **1.2.6**
 
 FIXES:
@@ -22,11 +31,11 @@ FIXES:
  * Fixes the expiration of the access token, if no idle-duration is
  * Fixed the forwarding proxy for SSL
  * Fixed the bug in the containedSubString method
- 
+
 BREAKING CHANGES:
- * Fixed up the config resource definition to use 'uri' not 'url' 
+ * Fixed up the config resource definition to use 'uri' not 'url'
  * Removed the --idle-duration option, was never really implemented well
- 
+
 #### **1.2.3**
 
 FEATURES:
@@ -36,7 +45,7 @@ FEATURES:
 
 TODO:
  * Need a means to updating the client certificate once expired.
- 
+
 CHANGES:
  * Updated the godeps for codegangsta cli to it's renamed version
 
@@ -71,25 +80,25 @@ FIXES:
 FIXES:
  * Added a auto build to quay.io on the travis build for master and tags
  * Fixed the host header to proxy to upstreams outside of the proxy domain (https://github.com/golang/go/issues/7618)
- * Adding a git+sha to the usage 
+ * Adding a git+sha to the usage
  * Defaulting to gin mode release unless verbose is true
  * Removed the gin debug logging for tests and builds
  * Removed the default upstream, as it caught people by surprise and some accidentally forwarded to themselves
  * Changed the state parameter (which is used as a redirect) to base64 the value allowing you to use complex urls
-  
+
 FEATURES:
  * Adding environment variables to some of the command line options
  * Adding the option of a forwarding agent, i.e. you can seat the proxy front of your application,
-   login to keycloak and use the proxy as forwarding agent to sign outbound requests. 
+   login to keycloak and use the proxy as forwarding agent to sign outbound requests.
  * Adding the version information into a header on /oauth/health endpoint
  * Removed the need to specify a client-secret, which means to cope with authz only or public endpoints
- * Added role url tokenizer, /auth/%role%/ will extract the role element and check the token as it 
+ * Added role url tokenizer, /auth/%role%/ will extract the role element and check the token as it
  * Added proxy protocol support for the listening socket (--enable-proxy-protocol=true)
  * Added the ability to listen on a unix socket
 
 BREAKING CHANGES:
  * Changed the X-Auth-Subject, it not is the actual subject from the token (makes more sense).
-   X-Auth-UserID will either be the subject id or the preferred username 
+   X-Auth-UserID will either be the subject id or the preferred username
 
 #### **1.0.6 (May 6th, 2016)**
 
@@ -104,25 +113,25 @@ FEATURES:
  * An additional option --add-claims to inject custom claims from the token into the authentication headers
    i.e. --add-claims=given_name would add X-Auth-Given-Name (assumed the claims exists)
  * Added the --secure-cookie option to control the 'secure' flag on the cookie
- 
+
 BREAKING CHANGES:
  * Changed the claims option from 'claims' to 'match-claims' (command line and config)
- * Changed keepalive config option to the same as the command line 'keepalive' -> 'upstream-keepalives' 
+ * Changed keepalive config option to the same as the command line 'keepalive' -> 'upstream-keepalives'
  * Changed the config option from 'upstream' to 'upstream-url', same as command line
- 
+
 #### **1.0.4 (April 30th, 2016)**
 
 FIXES:
  * Fixes the cookie sessions expiration
 
 FEATURES:
- * Adding a idle duration configuration option which controls the expiration of access token cookie and thus session. 
-   If the session is not used within that period, the session is removed. 
+ * Adding a idle duration configuration option which controls the expiration of access token cookie and thus session.
+   If the session is not used within that period, the session is removed.
  * The upstream endpoint has also be a unix socket
- 
+
 BREAKING CHANGES:
  * Change the client id in json/yaml config file from clientid -> client-id
- 
+
 #### **1.0.2 (April 22th, 2016)**
 
 FIXES:
diff --git a/cli.go b/cli.go
index 84ccffb..4fbe745 100644
--- a/cli.go
+++ b/cli.go
@@ -163,6 +163,10 @@ func getCLIOptions() []cli.Flag {
 			Usage: "specifies the keep-alive period for an active network connection",
 			Value: defaults.UpstreamKeepaliveTimeout,
 		},
+		cli.BoolTFlag{
+			Name:  "enable-authorization-header",
+			Usage: "adds the authorization header to the proxy request",
+		},
 		cli.BoolFlag{
 			Name:  "enable-refresh-tokens",
 			Usage: "enables the handling of the refresh tokens",
@@ -423,6 +427,9 @@ func parseCLIOptions(cx *cli.Context, config *Config) (err error) {
 	if cx.IsSet("enable-forwarding") {
 		config.EnableForwarding = cx.Bool("enable-forwarding")
 	}
+	if cx.IsSet("enable-authorization-header") {
+		config.EnableAuthorizationHeader = cx.Bool("enable-authorization-header")
+	}
 	if cx.IsSet("enable-refresh-tokens") {
 		config.EnableRefreshTokens = cx.Bool("enable-refresh-tokens")
 	}
diff --git a/config.go b/config.go
index 682c0e7..173e81d 100644
--- a/config.go
+++ b/config.go
@@ -27,16 +27,17 @@ import (
 // newDefaultConfig returns a initialized config
 func newDefaultConfig() *Config {
 	return &Config{
-		TagData:                  make(map[string]string, 0),
-		MatchClaims:              make(map[string]string, 0),
-		Headers:                  make(map[string]string, 0),
-		UpstreamTimeout:          time.Duration(10) * time.Second,
-		UpstreamKeepaliveTimeout: time.Duration(10) * time.Second,
-		CookieAccessName:         "kc-access",
-		CookieRefreshName:        "kc-state",
-		SecureCookie:             true,
-		SkipUpstreamTLSVerify:    true,
-		CrossOrigin:              CORS{},
+		TagData:                   make(map[string]string, 0),
+		MatchClaims:               make(map[string]string, 0),
+		Headers:                   make(map[string]string, 0),
+		UpstreamTimeout:           time.Duration(10) * time.Second,
+		UpstreamKeepaliveTimeout:  time.Duration(10) * time.Second,
+		EnableAuthorizationHeader: true,
+		CookieAccessName:          "kc-access",
+		CookieRefreshName:         "kc-state",
+		SecureCookie:              true,
+		SkipUpstreamTLSVerify:     true,
+		CrossOrigin:               CORS{},
 	}
 }
 
diff --git a/doc.go b/doc.go
index c9812cf..79dd5f7 100644
--- a/doc.go
+++ b/doc.go
@@ -24,7 +24,7 @@ import (
 )
 
 var (
-	release = "v1.2.6"
+	release = "v1.2.7"
 	gitsha  = "no gitsha provided"
 	version = release + " (git+sha: " + gitsha + ")"
 )
@@ -130,6 +130,9 @@ type Config struct {
 	// LocalhostMetrics indicated the metrics can only be consume via localhost
 	LocalhostMetrics bool `json:"localhost-only-metrics" yaml:"localhost-only-metrics"`
 
+	// EnableAuthorizationHeader indicates we should pass the authorization header
+	EnableAuthorizationHeader bool `json:"enable-authorization-header" yaml:"enable-authorization-header"`
+
 	// CookieDomain is a list of domains the cookie is available to
 	CookieDomain string `json:"cookie-domain" yaml:"cookie-domain"`
 	// CookieAccessName is the name of the access cookie holding the access token
diff --git a/middleware.go b/middleware.go
index bb62875..32b836c 100644
--- a/middleware.go
+++ b/middleware.go
@@ -427,7 +427,11 @@ func (r *oauthProxy) headersMiddleware(custom []string) gin.HandlerFunc {
 			cx.Request.Header.Add("X-Auth-ExpiresIn", id.expiresAt.String())
 			cx.Request.Header.Add("X-Auth-Token", id.token.Encode())
 			cx.Request.Header.Add("X-Auth-Roles", strings.Join(id.roles, ","))
-			cx.Request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", id.token.Encode()))
+
+			// step: add the authorization header if requested
+			if r.config.EnableAuthorizationHeader {
+				cx.Request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", id.token.Encode()))
+			}
 
 			// step: inject any custom claims
 			for claim, header := range customClaims {
diff --git a/middleware_test.go b/middleware_test.go
index 6c92988..a1feb99 100644
--- a/middleware_test.go
+++ b/middleware_test.go
@@ -327,6 +327,57 @@ func TestCustomHeadersHandler(t *testing.T) {
 	}
 }
 
+func TestHeaderMiddlewareAuthorizationHeader(t *testing.T) {
+	cases := []struct {
+		Identity *userContext
+		Expected http.Header
+		Enabled  bool
+	}{
+		{
+			Enabled: true,
+			Identity: &userContext{
+				email: "gambol99@gmail.com",
+			},
+			Expected: http.Header{
+				"X-Auth-Email":  []string{"gambol99@gmail.com"},
+				"Authorization": []string{"Bearer .."},
+			},
+		},
+		{
+			Enabled: false,
+			Identity: &userContext{
+				email: "gambol99@gmail.com",
+			},
+			Expected: http.Header{
+				"X-Auth-Email":  []string{"gambol99@gmail.com"},
+				"Authorization": []string{""},
+			},
+		},
+	}
+	for i, x := range cases {
+		config := newFakeKeycloakConfig()
+		config.EnableAuthorizationHeader = x.Enabled
+
+		// step: create the test proxy
+		p, _, _ := newTestProxyService(config)
+		context := newFakeGinContext("GET", "/test_url")
+		if x.Identity != nil {
+			context.Set(userContextName, x.Identity)
+		}
+
+		// step: create a middleware handler
+		handler := p.headersMiddleware([]string{})
+		handler(context)
+
+		// step: and check we have all the headers
+		for k := range x.Expected {
+			assert.Equal(t, x.Expected.Get(k), context.Request.Header.Get(k),
+				"case %d, expected (%s: %s) got: (%s: %s)",
+				i, k, x.Expected.Get(k), k, context.Request.Header.Get(k))
+		}
+	}
+}
+
 func TestAdmissionHandlerRoles(t *testing.T) {
 	proxy := newFakeKeycloakProxyWithResources(t, []*Resource{
 		{
diff --git a/server.go b/server.go
index 46e539b..1fc3b45 100644
--- a/server.go
+++ b/server.go
@@ -275,7 +275,6 @@ func (r *oauthProxy) createForwardingProxy() error {
 // Run starts the proxy service
 //
 func (r *oauthProxy) Run() error {
-	var err error
 	tlsConfig := &tls.Config{}
 
 	// step: are we doing mutual tls?
@@ -299,6 +298,7 @@ func (r *oauthProxy) Run() error {
 
 	// step: create the listener
 	var listener net.Listener
+	var err error
 	switch strings.HasPrefix(r.config.Listen, "unix://") {
 	case true:
 		socket := strings.Trim(r.config.Listen, "unix://")
diff --git a/server_test.go b/server_test.go
index 8c2741f..d54edcd 100644
--- a/server_test.go
+++ b/server_test.go
@@ -128,16 +128,19 @@ func getFakeRealmAccessToken(t *testing.T) jose.JWT {
 
 func newFakeKeycloakConfig() *Config {
 	return &Config{
-		DiscoveryURL:          "127.0.0.1:8080",
-		ClientID:              fakeClientID,
-		ClientSecret:          fakeSecret,
-		EncryptionKey:         "AgXa7xRcoClDEU0ZDSH4X0XhL5Qy2Z2j",
-		SkipTokenVerification: true,
-		Scopes:                []string{},
-		EnableRefreshTokens:   false,
-		SecureCookie:          false,
-		CookieAccessName:      "kc-access",
-		CookieRefreshName:     "kc-state",
+		ClientID:                  fakeClientID,
+		ClientSecret:              fakeSecret,
+		CookieAccessName:          "kc-access",
+		CookieRefreshName:         "kc-state",
+		DiscoveryURL:              "127.0.0.1:8080",
+		EnableAuthorizationHeader: true,
+		EnableRefreshTokens:       false,
+		EncryptionKey:             "AgXa7xRcoClDEU0ZDSH4X0XhL5Qy2Z2j",
+		LogRequests:               true,
+		Scopes:                    []string{},
+		SecureCookie:              false,
+		SkipTokenVerification:     false,
+		Verbose:                   false,
 		Resources: []*Resource{
 			{
 				URL:     fakeAdminRoleURL,
@@ -194,11 +197,8 @@ func newTestProxyService(config *Config) (*oauthProxy, *fakeOAuthServer, string)
 	}
 
 	// step: set the config
-	config.LogRequests = true
-	config.SkipTokenVerification = false
 	config.DiscoveryURL = auth.getLocation()
 	config.RevocationEndpoint = auth.getRevocationURL()
-	config.Verbose = false
 
 	// step: create a proxy
 	proxy, err := newProxy(config)
@@ -231,7 +231,10 @@ func newFakeKeycloakProxyWithResources(t *testing.T, resources []*Resource) *oau
 }
 
 func TestNewKeycloakProxy(t *testing.T) {
-	proxy, err := newProxy(newFakeKeycloakConfig())
+	cfg := newFakeKeycloakConfig()
+	cfg.DiscoveryURL = newFakeOAuthServer().getLocation()
+
+	proxy, err := newProxy(cfg)
 	assert.NoError(t, err)
 	assert.NotNil(t, proxy)
 	assert.NotNil(t, proxy.config)
-- 
GitLab