Fix access log (#14475)
Fix #14121, #14478. The `AccessLog` middleware has to be after `Contexter` or `APIContexter` so that we can get `LoginUserName` if possible. And also there is a **BREAK** change that it removed internal API access log.
This commit is contained in:
		
							parent
							
								
									4c6e029506
								
							
						
					
					
						commit
						a51cc6dea4
					
				
					 10 changed files with 129 additions and 72 deletions
				
			
		|  | @ -12,6 +12,7 @@ import ( | ||||||
| 
 | 
 | ||||||
| 	"code.gitea.io/gitea/models" | 	"code.gitea.io/gitea/models" | ||||||
| 	"code.gitea.io/gitea/modules/log" | 	"code.gitea.io/gitea/modules/log" | ||||||
|  | 	"code.gitea.io/gitea/modules/middlewares" | ||||||
| 	"code.gitea.io/gitea/modules/timeutil" | 	"code.gitea.io/gitea/modules/timeutil" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -121,7 +122,7 @@ func (o *OAuth2) VerifyAuthData(req *http.Request, w http.ResponseWriter, store | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if isInternalPath(req) || !isAPIPath(req) && !isAttachmentDownload(req) { | 	if middlewares.IsInternalPath(req) || !middlewares.IsAPIPath(req) && !isAttachmentDownload(req) { | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -94,16 +94,6 @@ func SessionUser(sess SessionStore) *models.User { | ||||||
| 	return user | 	return user | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // isAPIPath returns true if the specified URL is an API path
 |  | ||||||
| func isAPIPath(req *http.Request) bool { |  | ||||||
| 	return strings.HasPrefix(req.URL.Path, "/api/") |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // isInternalPath returns true if the specified URL is an internal API path
 |  | ||||||
| func isInternalPath(req *http.Request) bool { |  | ||||||
| 	return strings.HasPrefix(req.URL.Path, "/api/internal/") |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // isAttachmentDownload check if request is a file download (GET) with URL to an attachment
 | // isAttachmentDownload check if request is a file download (GET) with URL to an attachment
 | ||||||
| func isAttachmentDownload(req *http.Request) bool { | func isAttachmentDownload(req *http.Request) bool { | ||||||
| 	return strings.HasPrefix(req.URL.Path, "/attachments/") && req.Method == "GET" | 	return strings.HasPrefix(req.URL.Path, "/attachments/") && req.Method == "GET" | ||||||
|  |  | ||||||
|  | @ -12,6 +12,7 @@ import ( | ||||||
| 	"code.gitea.io/gitea/models" | 	"code.gitea.io/gitea/models" | ||||||
| 	"code.gitea.io/gitea/modules/base" | 	"code.gitea.io/gitea/modules/base" | ||||||
| 	"code.gitea.io/gitea/modules/log" | 	"code.gitea.io/gitea/modules/log" | ||||||
|  | 	"code.gitea.io/gitea/modules/middlewares" | ||||||
| 	"code.gitea.io/gitea/modules/setting" | 	"code.gitea.io/gitea/modules/setting" | ||||||
| 	"code.gitea.io/gitea/modules/templates" | 	"code.gitea.io/gitea/modules/templates" | ||||||
| 
 | 
 | ||||||
|  | @ -135,7 +136,7 @@ func (s *SSPI) VerifyAuthData(req *http.Request, w http.ResponseWriter, store Da | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Make sure requests to API paths and PWA resources do not create a new session
 | 	// Make sure requests to API paths and PWA resources do not create a new session
 | ||||||
| 	if !isAPIPath(req) && !isAttachmentDownload(req) { | 	if !middlewares.IsAPIPath(req) && !isAttachmentDownload(req) { | ||||||
| 		handleSignIn(w, req, sess, user) | 		handleSignIn(w, req, sess, user) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -166,9 +167,9 @@ func (s *SSPI) shouldAuthenticate(req *http.Request) (shouldAuth bool) { | ||||||
| 		} else if req.FormValue("auth_with_sspi") == "1" { | 		} else if req.FormValue("auth_with_sspi") == "1" { | ||||||
| 			shouldAuth = true | 			shouldAuth = true | ||||||
| 		} | 		} | ||||||
| 	} else if isInternalPath(req) { | 	} else if middlewares.IsInternalPath(req) { | ||||||
| 		shouldAuth = false | 		shouldAuth = false | ||||||
| 	} else if isAPIPath(req) || isAttachmentDownload(req) { | 	} else if middlewares.IsAPIPath(req) || isAttachmentDownload(req) { | ||||||
| 		shouldAuth = true | 		shouldAuth = true | ||||||
| 	} | 	} | ||||||
| 	return | 	return | ||||||
|  |  | ||||||
							
								
								
									
										60
									
								
								modules/context/access_log.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								modules/context/access_log.go
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,60 @@ | ||||||
|  | // Copyright 2020 The Gitea Authors. All rights reserved.
 | ||||||
|  | // Use of this source code is governed by a MIT-style
 | ||||||
|  | // license that can be found in the LICENSE file.
 | ||||||
|  | 
 | ||||||
|  | package context | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"html/template" | ||||||
|  | 	"net/http" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"code.gitea.io/gitea/modules/log" | ||||||
|  | 	"code.gitea.io/gitea/modules/setting" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type routerLoggerOptions struct { | ||||||
|  | 	req            *http.Request | ||||||
|  | 	Identity       *string | ||||||
|  | 	Start          *time.Time | ||||||
|  | 	ResponseWriter http.ResponseWriter | ||||||
|  | 	Ctx            map[string]interface{} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // AccessLogger returns a middleware to log access logger
 | ||||||
|  | func AccessLogger() func(http.Handler) http.Handler { | ||||||
|  | 	logger := log.GetLogger("access") | ||||||
|  | 	logTemplate, _ := template.New("log").Parse(setting.AccessLogTemplate) | ||||||
|  | 	return func(next http.Handler) http.Handler { | ||||||
|  | 		return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { | ||||||
|  | 			start := time.Now() | ||||||
|  | 			next.ServeHTTP(w, req) | ||||||
|  | 			identity := "-" | ||||||
|  | 			if val := SignedUserName(req); val != "" { | ||||||
|  | 				identity = val | ||||||
|  | 			} | ||||||
|  | 			rw := w.(ResponseWriter) | ||||||
|  | 
 | ||||||
|  | 			buf := bytes.NewBuffer([]byte{}) | ||||||
|  | 			err := logTemplate.Execute(buf, routerLoggerOptions{ | ||||||
|  | 				req:            req, | ||||||
|  | 				Identity:       &identity, | ||||||
|  | 				Start:          &start, | ||||||
|  | 				ResponseWriter: rw, | ||||||
|  | 				Ctx: map[string]interface{}{ | ||||||
|  | 					"RemoteAddr": req.RemoteAddr, | ||||||
|  | 					"Req":        req, | ||||||
|  | 				}, | ||||||
|  | 			}) | ||||||
|  | 			if err != nil { | ||||||
|  | 				log.Error("Could not set up chi access logger: %v", err.Error()) | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			err = logger.SendLog(log.INFO, "", "", 0, buf.String(), "") | ||||||
|  | 			if err != nil { | ||||||
|  | 				log.Error("Could not set up chi access logger: %v", err.Error()) | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -485,6 +485,31 @@ func GetContext(req *http.Request) *Context { | ||||||
| 	return req.Context().Value(contextKey).(*Context) | 	return req.Context().Value(contextKey).(*Context) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // SignedUserName returns signed user's name via context
 | ||||||
|  | func SignedUserName(req *http.Request) string { | ||||||
|  | 	if middlewares.IsInternalPath(req) { | ||||||
|  | 		return "" | ||||||
|  | 	} | ||||||
|  | 	if middlewares.IsAPIPath(req) { | ||||||
|  | 		ctx, ok := req.Context().Value(apiContextKey).(*APIContext) | ||||||
|  | 		if ok { | ||||||
|  | 			v := ctx.Data["SignedUserName"] | ||||||
|  | 			if res, ok := v.(string); ok { | ||||||
|  | 				return res | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		ctx, ok := req.Context().Value(contextKey).(*Context) | ||||||
|  | 		if ok { | ||||||
|  | 			v := ctx.Data["SignedUserName"] | ||||||
|  | 			if res, ok := v.(string); ok { | ||||||
|  | 				return res | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return "" | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func getCsrfOpts() CsrfOptions { | func getCsrfOpts() CsrfOptions { | ||||||
| 	return CsrfOptions{ | 	return CsrfOptions{ | ||||||
| 		Secret:         setting.SecretKey, | 		Secret:         setting.SecretKey, | ||||||
|  |  | ||||||
|  | @ -12,6 +12,7 @@ type ResponseWriter interface { | ||||||
| 	Flush() | 	Flush() | ||||||
| 	Status() int | 	Status() int | ||||||
| 	Before(func(ResponseWriter)) | 	Before(func(ResponseWriter)) | ||||||
|  | 	Size() int | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var ( | var ( | ||||||
|  | @ -21,11 +22,17 @@ var ( | ||||||
| // Response represents a response
 | // Response represents a response
 | ||||||
| type Response struct { | type Response struct { | ||||||
| 	http.ResponseWriter | 	http.ResponseWriter | ||||||
|  | 	written        int | ||||||
| 	status         int | 	status         int | ||||||
| 	befores        []func(ResponseWriter) | 	befores        []func(ResponseWriter) | ||||||
| 	beforeExecuted bool | 	beforeExecuted bool | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // Size return written size
 | ||||||
|  | func (r *Response) Size() int { | ||||||
|  | 	return r.written | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // Write writes bytes to HTTP endpoint
 | // Write writes bytes to HTTP endpoint
 | ||||||
| func (r *Response) Write(bs []byte) (int, error) { | func (r *Response) Write(bs []byte) (int, error) { | ||||||
| 	if !r.beforeExecuted { | 	if !r.beforeExecuted { | ||||||
|  | @ -35,8 +42,9 @@ func (r *Response) Write(bs []byte) (int, error) { | ||||||
| 		r.beforeExecuted = true | 		r.beforeExecuted = true | ||||||
| 	} | 	} | ||||||
| 	size, err := r.ResponseWriter.Write(bs) | 	size, err := r.ResponseWriter.Write(bs) | ||||||
|  | 	r.written += size | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return 0, err | 		return size, err | ||||||
| 	} | 	} | ||||||
| 	if r.status == 0 { | 	if r.status == 0 { | ||||||
| 		r.WriteHeader(200) | 		r.WriteHeader(200) | ||||||
|  |  | ||||||
							
								
								
									
										20
									
								
								modules/middlewares/request.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								modules/middlewares/request.go
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,20 @@ | ||||||
|  | // Copyright 2020 The Gitea Authors. All rights reserved.
 | ||||||
|  | // Use of this source code is governed by a MIT-style
 | ||||||
|  | // license that can be found in the LICENSE file.
 | ||||||
|  | 
 | ||||||
|  | package middlewares | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"net/http" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // IsAPIPath returns true if the specified URL is an API path
 | ||||||
|  | func IsAPIPath(req *http.Request) bool { | ||||||
|  | 	return strings.HasPrefix(req.URL.Path, "/api/") | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // IsInternalPath returns true if the specified URL is an internal API path
 | ||||||
|  | func IsInternalPath(req *http.Request) bool { | ||||||
|  | 	return strings.HasPrefix(req.URL.Path, "/api/internal/") | ||||||
|  | } | ||||||
|  | @ -553,6 +553,11 @@ func Routes() *web.Route { | ||||||
| 		})) | 		})) | ||||||
| 	} | 	} | ||||||
| 	m.Use(context.APIContexter()) | 	m.Use(context.APIContexter()) | ||||||
|  | 
 | ||||||
|  | 	if setting.EnableAccessLog { | ||||||
|  | 		m.Use(context.AccessLogger()) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	m.Use(context.ToggleAPI(&context.ToggleOptions{ | 	m.Use(context.ToggleAPI(&context.ToggleOptions{ | ||||||
| 		SignInRequired: setting.Service.RequireSignInView, | 		SignInRequired: setting.Service.RequireSignInView, | ||||||
| 	})) | 	})) | ||||||
|  |  | ||||||
|  | @ -5,7 +5,6 @@ | ||||||
| package routes | package routes | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"bytes" |  | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"io" | 	"io" | ||||||
|  | @ -13,7 +12,6 @@ import ( | ||||||
| 	"os" | 	"os" | ||||||
| 	"path" | 	"path" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"text/template" |  | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
| 	"code.gitea.io/gitea/modules/auth/sso" | 	"code.gitea.io/gitea/modules/auth/sso" | ||||||
|  | @ -28,57 +26,6 @@ import ( | ||||||
| 	"gitea.com/go-chi/session" | 	"gitea.com/go-chi/session" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type routerLoggerOptions struct { |  | ||||||
| 	req            *http.Request |  | ||||||
| 	Identity       *string |  | ||||||
| 	Start          *time.Time |  | ||||||
| 	ResponseWriter http.ResponseWriter |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // SignedUserName returns signed user's name via context
 |  | ||||||
| func SignedUserName(req *http.Request) string { |  | ||||||
| 	ctx := context.GetContext(req) |  | ||||||
| 	if ctx != nil { |  | ||||||
| 		v := ctx.Data["SignedUserName"] |  | ||||||
| 		if res, ok := v.(string); ok { |  | ||||||
| 			return res |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return "" |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func accessLogger() func(http.Handler) http.Handler { |  | ||||||
| 	logger := log.GetLogger("access") |  | ||||||
| 	logTemplate, _ := template.New("log").Parse(setting.AccessLogTemplate) |  | ||||||
| 	return func(next http.Handler) http.Handler { |  | ||||||
| 		return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { |  | ||||||
| 			start := time.Now() |  | ||||||
| 			next.ServeHTTP(w, req) |  | ||||||
| 			identity := "-" |  | ||||||
| 			if val := SignedUserName(req); val != "" { |  | ||||||
| 				identity = val |  | ||||||
| 			} |  | ||||||
| 			rw := w |  | ||||||
| 
 |  | ||||||
| 			buf := bytes.NewBuffer([]byte{}) |  | ||||||
| 			err := logTemplate.Execute(buf, routerLoggerOptions{ |  | ||||||
| 				req:            req, |  | ||||||
| 				Identity:       &identity, |  | ||||||
| 				Start:          &start, |  | ||||||
| 				ResponseWriter: rw, |  | ||||||
| 			}) |  | ||||||
| 			if err != nil { |  | ||||||
| 				log.Error("Could not set up chi access logger: %v", err.Error()) |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			err = logger.SendLog(log.INFO, "", "", 0, buf.String(), "") |  | ||||||
| 			if err != nil { |  | ||||||
| 				log.Error("Could not set up chi access logger: %v", err.Error()) |  | ||||||
| 			} |  | ||||||
| 		}) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // LoggerHandler is a handler that will log the routing to the default gitea log
 | // LoggerHandler is a handler that will log the routing to the default gitea log
 | ||||||
| func LoggerHandler(level log.Level) func(next http.Handler) http.Handler { | func LoggerHandler(level log.Level) func(next http.Handler) http.Handler { | ||||||
| 	return func(next http.Handler) http.Handler { | 	return func(next http.Handler) http.Handler { | ||||||
|  |  | ||||||
|  | @ -88,10 +88,6 @@ func commonMiddlewares() []func(http.Handler) http.Handler { | ||||||
| 			next.ServeHTTP(resp, req) | 			next.ServeHTTP(resp, req) | ||||||
| 		}) | 		}) | ||||||
| 	}) | 	}) | ||||||
| 
 |  | ||||||
| 	if setting.EnableAccessLog { |  | ||||||
| 		handlers = append(handlers, accessLogger()) |  | ||||||
| 	} |  | ||||||
| 	return handlers | 	return handlers | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -168,6 +164,10 @@ func WebRoutes() *web.Route { | ||||||
| 	r.Use(context.Contexter()) | 	r.Use(context.Contexter()) | ||||||
| 	// Removed: SetAutoHead allow a get request redirect to head if get method is not exist
 | 	// Removed: SetAutoHead allow a get request redirect to head if get method is not exist
 | ||||||
| 
 | 
 | ||||||
|  | 	if setting.EnableAccessLog { | ||||||
|  | 		r.Use(context.AccessLogger()) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	r.Use(user.GetNotificationCount) | 	r.Use(user.GetNotificationCount) | ||||||
| 	r.Use(repo.GetActiveStopwatch) | 	r.Use(repo.GetActiveStopwatch) | ||||||
| 	r.Use(func(ctx *context.Context) { | 	r.Use(func(ctx *context.Context) { | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue