Skip to content

Commit

Permalink
Add support for authentication based on reverse proxy email (#19949)
Browse files Browse the repository at this point in the history
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 `[email protected]`. 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: [email protected]"

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

Co-authored-by: Lunny Xiao <[email protected]>
Co-authored-by: 6543 <[email protected]>
  • Loading branch information
3 people authored Sep 28, 2022
1 parent 889a41c commit 64c3d55
Showing 1 changed file with 52 additions and 8 deletions.
60 changes: 52 additions & 8 deletions services/auth/reverseproxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,26 +37,22 @@ 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
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
Expand All @@ -71,6 +67,54 @@ 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 {
return strings.TrimSpace(req.Header.Get(setting.ReverseProxyAuthEmail))
}

// 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) {
Expand Down

0 comments on commit 64c3d55

Please sign in to comment.