mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 21:28:11 +09:00 
			
		
		
		
	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:
		| @@ -12,6 +12,7 @@ import ( | ||||
|  | ||||
| 	"code.gitea.io/gitea/models" | ||||
| 	"code.gitea.io/gitea/modules/log" | ||||
| 	"code.gitea.io/gitea/modules/middlewares" | ||||
| 	"code.gitea.io/gitea/modules/timeutil" | ||||
| ) | ||||
|  | ||||
| @@ -121,7 +122,7 @@ func (o *OAuth2) VerifyAuthData(req *http.Request, w http.ResponseWriter, store | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	if isInternalPath(req) || !isAPIPath(req) && !isAttachmentDownload(req) { | ||||
| 	if middlewares.IsInternalPath(req) || !middlewares.IsAPIPath(req) && !isAttachmentDownload(req) { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -94,16 +94,6 @@ func SessionUser(sess SessionStore) *models.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 | ||||
| func isAttachmentDownload(req *http.Request) bool { | ||||
| 	return strings.HasPrefix(req.URL.Path, "/attachments/") && req.Method == "GET" | ||||
|   | ||||
| @@ -12,6 +12,7 @@ import ( | ||||
| 	"code.gitea.io/gitea/models" | ||||
| 	"code.gitea.io/gitea/modules/base" | ||||
| 	"code.gitea.io/gitea/modules/log" | ||||
| 	"code.gitea.io/gitea/modules/middlewares" | ||||
| 	"code.gitea.io/gitea/modules/setting" | ||||
| 	"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 | ||||
| 	if !isAPIPath(req) && !isAttachmentDownload(req) { | ||||
| 	if !middlewares.IsAPIPath(req) && !isAttachmentDownload(req) { | ||||
| 		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" { | ||||
| 			shouldAuth = true | ||||
| 		} | ||||
| 	} else if isInternalPath(req) { | ||||
| 	} else if middlewares.IsInternalPath(req) { | ||||
| 		shouldAuth = false | ||||
| 	} else if isAPIPath(req) || isAttachmentDownload(req) { | ||||
| 	} else if middlewares.IsAPIPath(req) || isAttachmentDownload(req) { | ||||
| 		shouldAuth = true | ||||
| 	} | ||||
| 	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) | ||||
| } | ||||
|  | ||||
| // 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 { | ||||
| 	return CsrfOptions{ | ||||
| 		Secret:         setting.SecretKey, | ||||
|   | ||||
| @@ -12,6 +12,7 @@ type ResponseWriter interface { | ||||
| 	Flush() | ||||
| 	Status() int | ||||
| 	Before(func(ResponseWriter)) | ||||
| 	Size() int | ||||
| } | ||||
|  | ||||
| var ( | ||||
| @@ -21,11 +22,17 @@ var ( | ||||
| // Response represents a response | ||||
| type Response struct { | ||||
| 	http.ResponseWriter | ||||
| 	written        int | ||||
| 	status         int | ||||
| 	befores        []func(ResponseWriter) | ||||
| 	beforeExecuted bool | ||||
| } | ||||
|  | ||||
| // Size return written size | ||||
| func (r *Response) Size() int { | ||||
| 	return r.written | ||||
| } | ||||
|  | ||||
| // Write writes bytes to HTTP endpoint | ||||
| func (r *Response) Write(bs []byte) (int, error) { | ||||
| 	if !r.beforeExecuted { | ||||
| @@ -35,8 +42,9 @@ func (r *Response) Write(bs []byte) (int, error) { | ||||
| 		r.beforeExecuted = true | ||||
| 	} | ||||
| 	size, err := r.ResponseWriter.Write(bs) | ||||
| 	r.written += size | ||||
| 	if err != nil { | ||||
| 		return 0, err | ||||
| 		return size, err | ||||
| 	} | ||||
| 	if r.status == 0 { | ||||
| 		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()) | ||||
|  | ||||
| 	if setting.EnableAccessLog { | ||||
| 		m.Use(context.AccessLogger()) | ||||
| 	} | ||||
|  | ||||
| 	m.Use(context.ToggleAPI(&context.ToggleOptions{ | ||||
| 		SignInRequired: setting.Service.RequireSignInView, | ||||
| 	})) | ||||
|   | ||||
| @@ -5,7 +5,6 @@ | ||||
| package routes | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| @@ -13,7 +12,6 @@ import ( | ||||
| 	"os" | ||||
| 	"path" | ||||
| 	"strings" | ||||
| 	"text/template" | ||||
| 	"time" | ||||
|  | ||||
| 	"code.gitea.io/gitea/modules/auth/sso" | ||||
| @@ -28,57 +26,6 @@ import ( | ||||
| 	"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 | ||||
| func LoggerHandler(level log.Level) 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) | ||||
| 		}) | ||||
| 	}) | ||||
|  | ||||
| 	if setting.EnableAccessLog { | ||||
| 		handlers = append(handlers, accessLogger()) | ||||
| 	} | ||||
| 	return handlers | ||||
| } | ||||
|  | ||||
| @@ -168,6 +164,10 @@ func WebRoutes() *web.Route { | ||||
| 	r.Use(context.Contexter()) | ||||
| 	// 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(repo.GetActiveStopwatch) | ||||
| 	r.Use(func(ctx *context.Context) { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user