code.gitea.io/gitea@v1.21.7/services/auth/reverseproxy.go (about)

     1  // Copyright 2014 The Gogs Authors. All rights reserved.
     2  // Copyright 2019 The Gitea Authors. All rights reserved.
     3  // SPDX-License-Identifier: MIT
     4  
     5  package auth
     6  
     7  import (
     8  	"net/http"
     9  	"strings"
    10  
    11  	user_model "code.gitea.io/gitea/models/user"
    12  	"code.gitea.io/gitea/modules/log"
    13  	"code.gitea.io/gitea/modules/setting"
    14  	"code.gitea.io/gitea/modules/util"
    15  	"code.gitea.io/gitea/modules/web/middleware"
    16  
    17  	gouuid "github.com/google/uuid"
    18  )
    19  
    20  // Ensure the struct implements the interface.
    21  var (
    22  	_ Method = &ReverseProxy{}
    23  )
    24  
    25  // ReverseProxyMethodName is the constant name of the ReverseProxy authentication method
    26  const ReverseProxyMethodName = "reverse_proxy"
    27  
    28  // ReverseProxy implements the Auth interface, but actually relies on
    29  // a reverse proxy for authentication of users.
    30  // On successful authentication the proxy is expected to populate the username in the
    31  // "setting.ReverseProxyAuthUser" header. Optionally it can also populate the email of the
    32  // user in the "setting.ReverseProxyAuthEmail" header.
    33  type ReverseProxy struct{}
    34  
    35  // getUserName extracts the username from the "setting.ReverseProxyAuthUser" header
    36  func (r *ReverseProxy) getUserName(req *http.Request) string {
    37  	return strings.TrimSpace(req.Header.Get(setting.ReverseProxyAuthUser))
    38  }
    39  
    40  // Name represents the name of auth method
    41  func (r *ReverseProxy) Name() string {
    42  	return ReverseProxyMethodName
    43  }
    44  
    45  // getUserFromAuthUser extracts the username from the "setting.ReverseProxyAuthUser" header
    46  // of the request and returns the corresponding user object for that name.
    47  // Verification of header data is not performed as it should have already been done by
    48  // the reverse proxy.
    49  // If a username is available in the "setting.ReverseProxyAuthUser" header an existing
    50  // user object is returned (populated with username or email found in header).
    51  // Returns nil if header is empty.
    52  func (r *ReverseProxy) getUserFromAuthUser(req *http.Request) (*user_model.User, error) {
    53  	username := r.getUserName(req)
    54  	if len(username) == 0 {
    55  		return nil, nil
    56  	}
    57  	log.Trace("ReverseProxy Authorization: Found username: %s", username)
    58  
    59  	user, err := user_model.GetUserByName(req.Context(), username)
    60  	if err != nil {
    61  		if !user_model.IsErrUserNotExist(err) || !r.isAutoRegisterAllowed() {
    62  			log.Error("GetUserByName: %v", err)
    63  			return nil, err
    64  		}
    65  		user = r.newUser(req)
    66  	}
    67  	return user, nil
    68  }
    69  
    70  // getEmail extracts the email from the "setting.ReverseProxyAuthEmail" header
    71  func (r *ReverseProxy) getEmail(req *http.Request) string {
    72  	return strings.TrimSpace(req.Header.Get(setting.ReverseProxyAuthEmail))
    73  }
    74  
    75  // getUserFromAuthEmail extracts the username from the "setting.ReverseProxyAuthEmail" header
    76  // of the request and returns the corresponding user object for that email.
    77  // Verification of header data is not performed as it should have already been done by
    78  // the reverse proxy.
    79  // If an email is available in the "setting.ReverseProxyAuthEmail" header an existing
    80  // user object is returned (populated with the email found in header).
    81  // Returns nil if header is empty or if "setting.EnableReverseProxyEmail" is disabled.
    82  func (r *ReverseProxy) getUserFromAuthEmail(req *http.Request) *user_model.User {
    83  	if !setting.Service.EnableReverseProxyEmail {
    84  		return nil
    85  	}
    86  	email := r.getEmail(req)
    87  	if len(email) == 0 {
    88  		return nil
    89  	}
    90  	log.Trace("ReverseProxy Authorization: Found email: %s", email)
    91  
    92  	user, err := user_model.GetUserByEmail(req.Context(), email)
    93  	if err != nil {
    94  		// Do not allow auto-registration, we don't have a username here
    95  		if !user_model.IsErrUserNotExist(err) {
    96  			log.Error("GetUserByEmail: %v", err)
    97  		}
    98  		return nil
    99  	}
   100  	return user
   101  }
   102  
   103  // Verify attempts to load a user object based on headers sent by the reverse proxy.
   104  // First it will attempt to load it based on the username (see docs for getUserFromAuthUser),
   105  // and failing that it will attempt to load it based on the email (see docs for getUserFromAuthEmail).
   106  // Returns nil if the headers are empty or the user is not found.
   107  func (r *ReverseProxy) Verify(req *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) (*user_model.User, error) {
   108  	user, err := r.getUserFromAuthUser(req)
   109  	if err != nil {
   110  		return nil, err
   111  	}
   112  	if user == nil {
   113  		user = r.getUserFromAuthEmail(req)
   114  		if user == nil {
   115  			return nil, nil
   116  		}
   117  	}
   118  
   119  	// Make sure requests to API paths, attachment downloads, git and LFS do not create a new session
   120  	if !middleware.IsAPIPath(req) && !isAttachmentDownload(req) && !isGitRawOrAttachOrLFSPath(req) {
   121  		if sess != nil && (sess.Get("uid") == nil || sess.Get("uid").(int64) != user.ID) {
   122  			handleSignIn(w, req, sess, user)
   123  		}
   124  	}
   125  	store.GetData()["IsReverseProxy"] = true
   126  
   127  	log.Trace("ReverseProxy Authorization: Logged in user %-v", user)
   128  	return user, nil
   129  }
   130  
   131  // isAutoRegisterAllowed checks if EnableReverseProxyAutoRegister setting is true
   132  func (r *ReverseProxy) isAutoRegisterAllowed() bool {
   133  	return setting.Service.EnableReverseProxyAutoRegister
   134  }
   135  
   136  // newUser creates a new user object for the purpose of automatic registration
   137  // and populates its name and email with the information present in request headers.
   138  func (r *ReverseProxy) newUser(req *http.Request) *user_model.User {
   139  	username := r.getUserName(req)
   140  	if len(username) == 0 {
   141  		return nil
   142  	}
   143  
   144  	email := gouuid.New().String() + "@localhost"
   145  	if setting.Service.EnableReverseProxyEmail {
   146  		webAuthEmail := req.Header.Get(setting.ReverseProxyAuthEmail)
   147  		if len(webAuthEmail) > 0 {
   148  			email = webAuthEmail
   149  		}
   150  	}
   151  
   152  	var fullname string
   153  	if setting.Service.EnableReverseProxyFullName {
   154  		fullname = req.Header.Get(setting.ReverseProxyAuthFullName)
   155  	}
   156  
   157  	user := &user_model.User{
   158  		Name:     username,
   159  		Email:    email,
   160  		FullName: fullname,
   161  	}
   162  
   163  	overwriteDefault := user_model.CreateUserOverwriteOptions{
   164  		IsActive: util.OptionalBoolTrue,
   165  	}
   166  
   167  	if err := user_model.CreateUser(req.Context(), user, &overwriteDefault); err != nil {
   168  		// FIXME: should I create a system notice?
   169  		log.Error("CreateUser: %v", err)
   170  		return nil
   171  	}
   172  
   173  	return user
   174  }