From 4f6f95eed8cda15df3ee78fb1fb8eddce9390be2 Mon Sep 17 00:00:00 2001 From: Hasnain Lakhani Date: Sun, 12 Jun 2022 12:58:05 -0700 Subject: [PATCH 1/2] Add support for authentication based on reverse proxy email. This is useful in scenarios where the reverse proxy may have knowledge of user emails, but does not know about usernames set on gitea, as in the feature request in #19948. I tested this by setting up a fresh gitea install with one user `mhl` and email `m.hasnain.lakhani@gmail.com`. I then created a private repo, and configured gitea to allow reverse proxy authentication. Via curl I confirmed that these two requests now work and return 200s: curl http://localhost:3000/mhl/private -I --header "X-Webauth-User: mhl" curl http://localhost:3000/mhl/private -I --header "X-Webauth-Email: m.hasnain.lakhani@gmail.com" Before this commit, the second request did not work. I also verified that if I provide an invalid email or user, a 404 is correctly returned as before Closes #19948 --- services/auth/reverseproxy.go | 58 +++++++++++++++++++++++++++++++++-- 1 file changed, 55 insertions(+), 3 deletions(-) diff --git a/services/auth/reverseproxy.go b/services/auth/reverseproxy.go index 05d6af78f1745..fdb3a1bbe1bef 100644 --- a/services/auth/reverseproxy.go +++ b/services/auth/reverseproxy.go @@ -49,14 +49,14 @@ func (r *ReverseProxy) Name() string { return ReverseProxyMethodName } -// Verify extracts the username from the "setting.ReverseProxyAuthUser" header +// getUserFromAuthUser extracts the username from the "setting.ReverseProxyAuthUser" header // of the request and returns the corresponding user object for that name. // Verification of header data is not performed as it should have already been done by -// the revese proxy. +// the reverse proxy. // If a username is available in the "setting.ReverseProxyAuthUser" header an existing // user object is returned (populated with username or email found in header). // Returns nil if header is empty. -func (r *ReverseProxy) Verify(req *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) *user_model.User { +func (r *ReverseProxy) getUserFromAuthUser(req *http.Request) *user_model.User { username := r.getUserName(req) if len(username) == 0 { return nil @@ -71,6 +71,58 @@ func (r *ReverseProxy) Verify(req *http.Request, w http.ResponseWriter, store Da } user = r.newUser(req) } + return user +} + +// getEmail extracts the email from the "setting.ReverseProxyAuthEmail" header +func (r *ReverseProxy) getEmail(req *http.Request) string { + webAuthEmail := strings.TrimSpace(req.Header.Get(setting.ReverseProxyAuthEmail)) + if len(webAuthEmail) == 0 { + return "" + } + return webAuthEmail +} + +// getUserFromAuthEmail extracts the username from the "setting.ReverseProxyAuthEmail" header +// of the request and returns the corresponding user object for that email. +// Verification of header data is not performed as it should have already been done by +// the reverse proxy. +// If an email is available in the "setting.ReverseProxyAuthEmail" header an existing +// user object is returned (populated with the email found in header). +// Returns nil if header is empty or if "setting.EnableReverseProxyEmail" is disabled. +func (r *ReverseProxy) getUserFromAuthEmail(req *http.Request) *user_model.User { + if !setting.Service.EnableReverseProxyEmail { + return nil + } + email := r.getEmail(req) + if len(email) == 0 { + return nil + } + log.Trace("ReverseProxy Authorization: Found email: %s", email) + + user, err := user_model.GetUserByEmail(email) + if err != nil { + // Do not allow auto-registration, we don't have a username here + if !user_model.IsErrUserNotExist(err) { + log.Error("GetUserByEmail: %v", err) + } + return nil + } + return user +} + +// Verify attempts to load a user object based on headers sent by the reverse proxy. +// First it will attempt to load it based on the username (see docs for getUserFromAuthUser), +// and failing that it will attempt to load it based on the email (see docs for getUserFromAuthEmail). +// Returns nil if the headers are empty or the user is not found. +func (r *ReverseProxy) Verify(req *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) *user_model.User { + user := r.getUserFromAuthUser(req) + if user == nil { + user = r.getUserFromAuthEmail(req) + if user == nil { + return nil + } + } // Make sure requests to API paths, attachment downloads, git and LFS do not create a new session if !middleware.IsAPIPath(req) && !isAttachmentDownload(req) && !isGitRawReleaseOrLFSPath(req) { From c2f6275e1dc1eb700115a78aab90e2e7fb6121e5 Mon Sep 17 00:00:00 2001 From: Hasnain Lakhani Date: Sat, 25 Jun 2022 11:32:08 -0700 Subject: [PATCH 2/2] reverseproxy.go: simplify getUserName and getEmail --- services/auth/reverseproxy.go | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/services/auth/reverseproxy.go b/services/auth/reverseproxy.go index fdb3a1bbe1bef..a79366b908207 100644 --- a/services/auth/reverseproxy.go +++ b/services/auth/reverseproxy.go @@ -37,11 +37,7 @@ type ReverseProxy struct{} // getUserName extracts the username from the "setting.ReverseProxyAuthUser" header func (r *ReverseProxy) getUserName(req *http.Request) string { - webAuthUser := strings.TrimSpace(req.Header.Get(setting.ReverseProxyAuthUser)) - if len(webAuthUser) == 0 { - return "" - } - return webAuthUser + return strings.TrimSpace(req.Header.Get(setting.ReverseProxyAuthUser)) } // Name represents the name of auth method @@ -76,11 +72,7 @@ func (r *ReverseProxy) getUserFromAuthUser(req *http.Request) *user_model.User { // getEmail extracts the email from the "setting.ReverseProxyAuthEmail" header func (r *ReverseProxy) getEmail(req *http.Request) string { - webAuthEmail := strings.TrimSpace(req.Header.Get(setting.ReverseProxyAuthEmail)) - if len(webAuthEmail) == 0 { - return "" - } - return webAuthEmail + return strings.TrimSpace(req.Header.Get(setting.ReverseProxyAuthEmail)) } // getUserFromAuthEmail extracts the username from the "setting.ReverseProxyAuthEmail" header