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 }