Repository: oauth2-proxy/oauth2-proxy
Pull request #413 (2020-02-24 09:36:17)

Add  set basic auth param
<!--- Provide a general summary of your changes in the Title above -->
Added the `-set-basic-auth` param to set the Basic Authorization header for upstreams.

## Description

<!--- Describe your changes in detail -->

## Motivation and Context
Problem and Context described in issue #394 

<!--- Why is this change required? What problem does it solve? -->
<!--- If it fixes an open issue, please link to the issue here. -->

## How Has This Been Tested?

Tested in our environments.
<!--- Please describe in detail how you tested your changes. -->
<!--- Include details of your testing environment, and the tests you ran to -->
<!--- see how your change affects other areas of the code, etc. -->

## Checklist:

<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->

- [x] My change requires a change to the documentation or CHANGELOG.
- [x] I have updated the documentation/CHANGELOG accordingly.
- [x] I have created a feature (non-master) branch for my PR.


morarucostel: @JoelSpeed sorry it took longer for the changes, I was focusing on something else.
I believe I covered all the comments.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7ed07159..8ff23ee3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -27,6 +27,7 @@
 - [#432](https://github.com/oauth2-proxy/oauth2-proxy/pull/432) Update ruby dependencies for documentation (@theobarberbany)
 - [#471](https://github.com/oauth2-proxy/oauth2-proxy/pull/471) Add logging in case of invalid redirects (@gargath)
 - [#462](https://github.com/oauth2-proxy/oauth2-proxy/pull/462) Allow HTML in banner message (@eritikass).
+- [#413](https://github.com/oauth2-proxy/oauth2-proxy/pull/413) Add -set-basic-auth param to set the Basic Authorization header for upstreams (@morarucostel).
 
 # v5.1.0
 
diff --git a/docs/configuration/configuration.md b/docs/configuration/configuration.md
index 6b61dc24..cb2db72c 100644
--- a/docs/configuration/configuration.md
+++ b/docs/configuration/configuration.md
@@ -103,6 +103,7 @@ An example [oauth2-proxy.cfg]({{ site.gitweb }}/contrib/oauth2-proxy.cfg.example
 | `-session-store-type` | string | [Session data storage backend](configuration/sessions); redis or cookie | cookie |
 | `-set-xauthrequest` | bool | set X-Auth-Request-User, X-Auth-Request-Email and X-Auth-Request-Preferred-Username response headers (useful in Nginx auth_request mode) | false |
 | `-set-authorization-header` | bool | set Authorization Bearer response header (useful in Nginx auth_request mode) | false |
+| `-set-basic-auth` | bool | set HTTP Basic Auth information in response (useful in Nginx auth_request mode) | true |
 | `-signature-key` | string | GAP-Signature request signature key (algorithm:secretkey) | |
 | `-silence-ping-logging` | bool | disable logging of requests to ping endpoint | false |
 | `-skip-auth-preflight` | bool | will skip authentication for OPTIONS requests | false |
diff --git a/main.go b/main.go
index 1af017c5..bcd6bec5 100644
--- a/main.go
+++ b/main.go
@@ -43,6 +43,7 @@ func main() {
 	flagSet.Bool("set-xauthrequest", false, "set X-Auth-Request-User and X-Auth-Request-Email response headers (useful in Nginx auth_request mode)")
 	flagSet.Var(&upstreams, "upstream", "the http url(s) of the upstream endpoint, file:// paths for static files or static://<status_code> for static response. Routing is based on the path")
 	flagSet.Bool("pass-basic-auth", true, "pass HTTP Basic Auth, X-Forwarded-User and X-Forwarded-Email information to upstream")
+	flagSet.Bool("set-basic-auth", true, "set HTTP Basic Auth information in response (useful in Nginx auth_request mode)")
 	flagSet.Bool("prefer-email-to-user", false, "Prefer to use the Email address as the Username when passing information to upstream. Will only use Username if Email is unavailable, eg. htaccess authentication. Used in conjunction with -pass-basic-auth and -pass-user-headers")
 	flagSet.Bool("pass-user-headers", true, "pass X-Forwarded-User and X-Forwarded-Email information to upstream")
 	flagSet.String("basic-auth-password", "", "the password to set when passing the HTTP Basic Auth header")
diff --git a/oauthproxy.go b/oauthproxy.go
index c5470544..dbe379a4 100644
--- a/oauthproxy.go
+++ b/oauthproxy.go
@@ -94,6 +94,7 @@ type OAuthProxy struct {
 	serveMux             http.Handler
 	SetXAuthRequest      bool
 	PassBasicAuth        bool
+	SetBasicAuth         bool
 	SkipProviderButton   bool
 	PassUserHeaders      bool
 	BasicAuthPassword    string
@@ -302,6 +303,7 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
 		compiledRegex:        opts.CompiledRegex,
 		SetXAuthRequest:      opts.SetXAuthRequest,
 		PassBasicAuth:        opts.PassBasicAuth,
+		SetBasicAuth:         opts.SetBasicAuth,
 		PassUserHeaders:      opts.PassUserHeaders,
 		BasicAuthPassword:    opts.BasicAuthPassword,
 		PassAccessToken:      opts.PassAccessToken,
@@ -1037,6 +1039,14 @@ func (p *OAuthProxy) addHeadersForProxying(rw http.ResponseWriter, req *http.Req
 			req.Header.Del("Authorization")
 		}
 	}
+	if p.SetBasicAuth {
+		if session.User != "" {
+			authVal := b64.StdEncoding.EncodeToString([]byte(session.User + ":" + p.BasicAuthPassword))
+			rw.Header().Set("Authorization", "Basic "+authVal)
+		} else {
+			rw.Header().Del("Authorization")
+		}
+	}
 	if p.SetAuthorization {
 		if session.IDToken != "" {
 			rw.Header().Set("Authorization", fmt.Sprintf("Bearer %s", session.IDToken))
diff --git a/options.go b/options.go
index b5cb8590..abfc6013 100644
--- a/options.go
+++ b/options.go
@@ -73,6 +73,7 @@ type Options struct {
 	SkipJwtBearerTokens           bool          `flag:"skip-jwt-bearer-tokens" cfg:"skip_jwt_bearer_tokens" env:"OAUTH2_PROXY_SKIP_JWT_BEARER_TOKENS"`
 	ExtraJwtIssuers               []string      `flag:"extra-jwt-issuers" cfg:"extra_jwt_issuers" env:"OAUTH2_PROXY_EXTRA_JWT_ISSUERS"`
 	PassBasicAuth                 bool          `flag:"pass-basic-auth" cfg:"pass_basic_auth" env:"OAUTH2_PROXY_PASS_BASIC_AUTH"`
+	SetBasicAuth                  bool          `flag:"set-basic-auth" cfg:"set_basic_auth" env:"OAUTH2_PROXY_SET_BASIC_AUTH"`
 	PreferEmailToUser             bool          `flag:"prefer-email-to-user" cfg:"prefer_email_to_user" env:"OAUTH2_PROXY_PREFER_EMAIL_TO_USER"`
 	BasicAuthPassword             string        `flag:"basic-auth-password" cfg:"basic_auth_password" env:"OAUTH2_PROXY_BASIC_AUTH_PASSWORD"`
 	PassAccessToken               bool          `flag:"pass-access-token" cfg:"pass_access_token" env:"OAUTH2_PROXY_PASS_ACCESS_TOKEN"`
@@ -166,6 +167,7 @@ func NewOptions() *Options {
 		SetXAuthRequest:                  false,
 		SkipAuthPreflight:                false,
 		PassBasicAuth:                    true,
+		SetBasicAuth:                     false,
 		PassUserHeaders:                  true,
 		PassAccessToken:                  false,
 		PassHostHeader:                   true,
@@ -243,6 +245,10 @@ func (o *Options) Validate() error {
 			"\n      use email-domain=* to authorize all email addresses")
 	}
 
+	if o.SetBasicAuth && o.SetAuthorization {
+		msgs = append(msgs, "mutually exclusive: set-basic-auth and set-authorization-header can not both be true")
+	}
+
 	if o.OIDCIssuerURL != "" {
 
 		ctx := context.Background()


Test patch:
diff --git a/oauthproxy_test.go b/oauthproxy_test.go
index ec46bdd8..86cfc90a 100644
--- a/oauthproxy_test.go
+++ b/oauthproxy_test.go
@@ -404,6 +404,7 @@ func TestBasicAuthPassword(t *testing.T) {
 	opts.ClientSecret = "alkgret"
 	opts.CookieSecure = false
 	opts.PassBasicAuth = true
+	opts.SetBasicAuth = true
 	opts.PassUserHeaders = true
 	opts.PreferEmailToUser = true
 	opts.BasicAuthPassword = "This is a secure password"
@@ -1075,6 +1076,71 @@ func TestAuthOnlyEndpointSetXAuthRequestHeaders(t *testing.T) {
 	assert.Equal(t, "oauth_user@example.com", pcTest.rw.HeaderMap["X-Auth-Request-Email"][0])
 }
 
+func TestAuthOnlyEndpointSetBasicAuthTrueRequestHeaders(t *testing.T) {
+	var pcTest ProcessCookieTest
+
+	pcTest.opts = NewOptions()
+	pcTest.opts.SetXAuthRequest = true
+	pcTest.opts.SetBasicAuth = true
+	pcTest.opts.Validate()
+
+	pcTest.proxy = NewOAuthProxy(pcTest.opts, func(email string) bool {
+		return pcTest.validateUser
+	})
+	pcTest.proxy.provider = &TestProvider{
+		ValidToken: true,
+	}
+
+	pcTest.validateUser = true
+
+	pcTest.rw = httptest.NewRecorder()
+	pcTest.req, _ = http.NewRequest("GET",
+		pcTest.opts.ProxyPrefix+"/auth", nil)
+
+	startSession := &sessions.SessionState{
+		User: "oauth_user", Email: "oauth_user@example.com", AccessToken: "oauth_token", CreatedAt: time.Now()}
+	pcTest.SaveSession(startSession)
+
+	pcTest.proxy.ServeHTTP(pcTest.rw, pcTest.req)
+	assert.Equal(t, http.StatusAccepted, pcTest.rw.Code)
+	assert.Equal(t, "oauth_user", pcTest.rw.HeaderMap["X-Auth-Request-User"][0])
+	assert.Equal(t, "oauth_user@example.com", pcTest.rw.HeaderMap["X-Auth-Request-Email"][0])
+	expectedHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte("oauth_user:"+pcTest.opts.BasicAuthPassword))
+	assert.Equal(t, expectedHeader, pcTest.rw.HeaderMap["Authorization"][0])
+}
+
+func TestAuthOnlyEndpointSetBasicAuthFalseRequestHeaders(t *testing.T) {
+	var pcTest ProcessCookieTest
+
+	pcTest.opts = NewOptions()
+	pcTest.opts.SetXAuthRequest = true
+	pcTest.opts.SetBasicAuth = false
+	pcTest.opts.Validate()
+
+	pcTest.proxy = NewOAuthProxy(pcTest.opts, func(email string) bool {
+		return pcTest.validateUser
+	})
+	pcTest.proxy.provider = &TestProvider{
+		ValidToken: true,
+	}
+
+	pcTest.validateUser = true
+
+	pcTest.rw = httptest.NewRecorder()
+	pcTest.req, _ = http.NewRequest("GET",
+		pcTest.opts.ProxyPrefix+"/auth", nil)
+
+	startSession := &sessions.SessionState{
+		User: "oauth_user", Email: "oauth_user@example.com", AccessToken: "oauth_token", CreatedAt: time.Now()}
+	pcTest.SaveSession(startSession)
+
+	pcTest.proxy.ServeHTTP(pcTest.rw, pcTest.req)
+	assert.Equal(t, http.StatusAccepted, pcTest.rw.Code)
+	assert.Equal(t, "oauth_user", pcTest.rw.HeaderMap["X-Auth-Request-User"][0])
+	assert.Equal(t, "oauth_user@example.com", pcTest.rw.HeaderMap["X-Auth-Request-Email"][0])
+	assert.Equal(t, 0, len(pcTest.rw.HeaderMap["Authorization"]), "should not have Authorization header entries")
+}
+
 func TestAuthSkippedForPreflightRequests(t *testing.T) {
 	upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		w.WriteHeader(200)