2016-03-14 00:37:44 +03:00
// Copyright 2016 The Gogs Authors. All rights reserved.
2019-03-19 05:29:43 +03:00
// Copyright 2019 The Gitea Authors. All rights reserved.
2022-11-27 21:20:29 +03:00
// SPDX-License-Identifier: MIT
2016-03-14 00:37:44 +03:00
package context
import (
2021-01-26 18:36:53 +03:00
"context"
2016-03-14 06:20:22 +03:00
"fmt"
2020-05-28 19:58:11 +03:00
"net/http"
2019-06-26 11:51:32 +03:00
"net/url"
2016-03-14 06:20:22 +03:00
"strings"
2022-01-02 16:12:35 +03:00
"code.gitea.io/gitea/models/auth"
2021-12-10 04:27:50 +03:00
repo_model "code.gitea.io/gitea/models/repo"
2022-05-02 16:35:45 +03:00
"code.gitea.io/gitea/modules/cache"
2019-03-27 12:33:00 +03:00
"code.gitea.io/gitea/modules/git"
2022-07-23 09:38:03 +03:00
"code.gitea.io/gitea/modules/httpcache"
2016-11-10 19:24:48 +03:00
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
2021-01-30 11:55:53 +03:00
"code.gitea.io/gitea/modules/web/middleware"
2016-03-14 00:37:44 +03:00
)
2021-01-29 18:35:30 +03:00
// APIContext is a specific context for API service
2016-03-14 00:37:44 +03:00
type APIContext struct {
* Context
2016-03-26 01:04:02 +03:00
Org * APIOrganization
2016-03-14 00:37:44 +03:00
}
2021-12-28 16:28:27 +03:00
// Currently, we have the following common fields in error response:
// * message: the message for end users (it shouldn't be used for error type detection)
// if we need to indicate some errors, we should introduce some new fields like ErrorCode or ErrorType
// * url: the swagger document URL
2017-05-02 16:35:59 +03:00
// APIError is error format response
// swagger:response error
type APIError struct {
Message string ` json:"message" `
URL string ` json:"url" `
}
// APIValidationError is error format response related to input validation
// swagger:response validationError
type APIValidationError struct {
Message string ` json:"message" `
URL string ` json:"url" `
}
2019-12-20 20:07:12 +03:00
// APIInvalidTopicsError is error format response to invalid topics
// swagger:response invalidTopicsError
type APIInvalidTopicsError struct {
2021-12-28 16:28:27 +03:00
Message string ` json:"message" `
InvalidTopics [ ] string ` json:"invalidTopics" `
2019-12-20 20:07:12 +03:00
}
2022-01-20 20:46:10 +03:00
// APIEmpty is an empty response
2017-05-02 16:35:59 +03:00
// swagger:response empty
type APIEmpty struct { }
2022-01-20 20:46:10 +03:00
// APIForbiddenError is a forbidden error response
2017-05-02 16:35:59 +03:00
// swagger:response forbidden
type APIForbiddenError struct {
APIError
}
2022-01-20 20:46:10 +03:00
// APINotFound is a not found empty response
2017-05-02 16:35:59 +03:00
// swagger:response notFound
type APINotFound struct { }
2022-01-20 20:46:10 +03:00
// APIConflict is a conflict empty response
2020-10-31 04:56:34 +03:00
// swagger:response conflict
type APIConflict struct { }
2022-01-20 20:46:10 +03:00
// APIRedirect is a redirect response
2017-08-21 14:13:47 +03:00
// swagger:response redirect
type APIRedirect struct { }
2022-01-20 20:46:10 +03:00
// APIString is a string response
2020-06-05 14:03:12 +03:00
// swagger:response string
type APIString string
2021-01-23 22:33:43 +03:00
// ServerError responds with error message, status is 500
func ( ctx * APIContext ) ServerError ( title string , err error ) {
ctx . Error ( http . StatusInternalServerError , title , err )
}
2020-05-28 19:58:11 +03:00
// Error responds with an error message to client with given obj as the message.
2016-03-14 01:49:16 +03:00
// If status is 500, also it prints error to log.
func ( ctx * APIContext ) Error ( status int , title string , obj interface { } ) {
var message string
if err , ok := obj . ( error ) ; ok {
message = err . Error ( )
} else {
2020-05-28 19:58:11 +03:00
message = fmt . Sprintf ( "%s" , obj )
2016-03-14 01:49:16 +03:00
}
2020-05-28 19:58:11 +03:00
if status == http . StatusInternalServerError {
log . ErrorWithSkip ( 1 , "%s: %s" , title , message )
2020-06-03 21:17:54 +03:00
2022-03-22 10:03:22 +03:00
if setting . IsProd && ! ( ctx . Doer != nil && ctx . Doer . IsAdmin ) {
2020-06-03 21:17:54 +03:00
message = ""
}
2016-03-14 01:49:16 +03:00
}
2017-05-02 16:35:59 +03:00
ctx . JSON ( status , APIError {
Message : message ,
2019-06-13 00:07:24 +03:00
URL : setting . API . SwaggerURL ,
2016-03-14 01:49:16 +03:00
} )
}
2020-05-28 19:58:11 +03:00
// InternalServerError responds with an error message to the client with the error as a message
// and the file and line of the caller.
func ( ctx * APIContext ) InternalServerError ( err error ) {
log . ErrorWithSkip ( 1 , "InternalServerError: %v" , err )
var message string
2022-03-22 10:03:22 +03:00
if ! setting . IsProd || ( ctx . Doer != nil && ctx . Doer . IsAdmin ) {
2020-05-28 19:58:11 +03:00
message = err . Error ( )
}
ctx . JSON ( http . StatusInternalServerError , APIError {
Message : message ,
URL : setting . API . SwaggerURL ,
} )
}
2021-12-28 16:28:27 +03:00
type apiContextKeyType struct { }
var apiContextKey = apiContextKeyType { }
2021-01-26 18:36:53 +03:00
// WithAPIContext set up api context in request
func WithAPIContext ( req * http . Request , ctx * APIContext ) * http . Request {
return req . WithContext ( context . WithValue ( req . Context ( ) , apiContextKey , ctx ) )
}
// GetAPIContext returns a context for API routes
func GetAPIContext ( req * http . Request ) * APIContext {
return req . Context ( ) . Value ( apiContextKey ) . ( * APIContext )
}
2019-06-26 11:51:32 +03:00
func genAPILinks ( curURL * url . URL , total , pageSize , curPage int ) [ ] string {
page := NewPagination ( total , pageSize , curPage , 0 )
2019-04-20 07:15:19 +03:00
paginater := page . Paginater
2016-03-14 06:20:22 +03:00
links := make ( [ ] string , 0 , 4 )
2019-06-26 11:51:32 +03:00
2019-04-20 07:15:19 +03:00
if paginater . HasNext ( ) {
2019-06-26 11:51:32 +03:00
u := * curURL
queries := u . Query ( )
queries . Set ( "page" , fmt . Sprintf ( "%d" , paginater . Next ( ) ) )
u . RawQuery = queries . Encode ( )
links = append ( links , fmt . Sprintf ( "<%s%s>; rel=\"next\"" , setting . AppURL , u . RequestURI ( ) [ 1 : ] ) )
2016-03-14 06:20:22 +03:00
}
2019-04-20 07:15:19 +03:00
if ! paginater . IsLast ( ) {
2019-06-26 11:51:32 +03:00
u := * curURL
queries := u . Query ( )
queries . Set ( "page" , fmt . Sprintf ( "%d" , paginater . TotalPages ( ) ) )
u . RawQuery = queries . Encode ( )
links = append ( links , fmt . Sprintf ( "<%s%s>; rel=\"last\"" , setting . AppURL , u . RequestURI ( ) [ 1 : ] ) )
2016-03-14 06:20:22 +03:00
}
2019-04-20 07:15:19 +03:00
if ! paginater . IsFirst ( ) {
2019-06-26 11:51:32 +03:00
u := * curURL
queries := u . Query ( )
queries . Set ( "page" , "1" )
u . RawQuery = queries . Encode ( )
links = append ( links , fmt . Sprintf ( "<%s%s>; rel=\"first\"" , setting . AppURL , u . RequestURI ( ) [ 1 : ] ) )
2016-03-14 06:20:22 +03:00
}
2019-04-20 07:15:19 +03:00
if paginater . HasPrevious ( ) {
2019-06-26 11:51:32 +03:00
u := * curURL
queries := u . Query ( )
queries . Set ( "page" , fmt . Sprintf ( "%d" , paginater . Previous ( ) ) )
u . RawQuery = queries . Encode ( )
links = append ( links , fmt . Sprintf ( "<%s%s>; rel=\"prev\"" , setting . AppURL , u . RequestURI ( ) [ 1 : ] ) )
2016-03-14 06:20:22 +03:00
}
2019-06-26 11:51:32 +03:00
return links
}
// SetLinkHeader sets pagination link header by given total number and page size.
func ( ctx * APIContext ) SetLinkHeader ( total , pageSize int ) {
2021-07-29 04:42:15 +03:00
links := genAPILinks ( ctx . Req . URL , total , pageSize , ctx . FormInt ( "page" ) )
2016-03-14 06:20:22 +03:00
if len ( links ) > 0 {
2021-12-15 09:59:57 +03:00
ctx . RespHeader ( ) . Set ( "Link" , strings . Join ( links , "," ) )
2021-08-12 15:43:08 +03:00
ctx . AppendAccessControlExposeHeaders ( "Link" )
}
}
2020-06-22 18:21:11 +03:00
// CheckForOTP validates OTP
2019-04-19 11:59:26 +03:00
func ( ctx * APIContext ) CheckForOTP ( ) {
2021-09-17 14:43:47 +03:00
if skip , ok := ctx . Data [ "SkipLocalTwoFA" ] ; ok && skip . ( bool ) {
return // Skip 2FA
}
2019-04-19 11:59:26 +03:00
otpHeader := ctx . Req . Header . Get ( "X-Gitea-OTP" )
2022-03-22 10:03:22 +03:00
twofa , err := auth . GetTwoFactorByUID ( ctx . Context . Doer . ID )
2019-04-19 11:59:26 +03:00
if err != nil {
2022-01-02 16:12:35 +03:00
if auth . IsErrTwoFactorNotEnrolled ( err ) {
2019-04-19 11:59:26 +03:00
return // No 2FA enrollment for this user
}
2021-04-05 18:30:52 +03:00
ctx . Context . Error ( http . StatusInternalServerError )
2019-04-19 11:59:26 +03:00
return
}
ok , err := twofa . ValidateTOTP ( otpHeader )
if err != nil {
2021-04-05 18:30:52 +03:00
ctx . Context . Error ( http . StatusInternalServerError )
2019-04-19 11:59:26 +03:00
return
}
if ! ok {
2022-03-23 07:54:07 +03:00
ctx . Context . Error ( http . StatusUnauthorized )
2019-04-19 11:59:26 +03:00
return
}
}
2021-01-29 18:35:30 +03:00
// APIContexter returns apicontext as middleware
2021-01-26 18:36:53 +03:00
func APIContexter ( ) func ( http . Handler ) http . Handler {
return func ( next http . Handler ) http . Handler {
return http . HandlerFunc ( func ( w http . ResponseWriter , req * http . Request ) {
2022-01-20 20:46:10 +03:00
locale := middleware . Locale ( w , req )
ctx := APIContext {
2021-01-26 18:36:53 +03:00
Context : & Context {
2022-04-08 07:22:10 +03:00
Resp : NewResponse ( w ) ,
Data : map [ string ] interface { } { } ,
Locale : locale ,
2022-05-02 16:35:45 +03:00
Cache : cache . GetCache ( ) ,
2021-01-26 18:36:53 +03:00
Repo : & Repository {
PullRequest : & PullRequest { } ,
} ,
Org : & Organization { } ,
} ,
Org : & APIOrganization { } ,
}
2022-05-05 17:13:23 +03:00
defer ctx . Close ( )
2021-01-26 18:36:53 +03:00
ctx . Req = WithAPIContext ( WithContext ( req , ctx . Context ) , & ctx )
// If request sends files, parse them here otherwise the Query() can't be parsed and the CsrfToken will be invalid.
if ctx . Req . Method == "POST" && strings . Contains ( ctx . Req . Header . Get ( "Content-Type" ) , "multipart/form-data" ) {
if err := ctx . Req . ParseMultipartForm ( setting . Attachment . MaxSize << 20 ) ; err != nil && ! strings . Contains ( err . Error ( ) , "EOF" ) { // 32MB max size
ctx . InternalServerError ( err )
return
}
}
2023-03-08 23:40:04 +03:00
httpcache . SetCacheControlInHeader ( ctx . Resp . Header ( ) , 0 , "no-transform" )
2021-08-06 23:47:10 +03:00
ctx . Resp . Header ( ) . Set ( ` X-Frame-Options ` , setting . CORSConfig . XFrameOptions )
2021-01-26 18:36:53 +03:00
2022-01-20 02:26:57 +03:00
ctx . Data [ "Context" ] = & ctx
2021-01-26 18:36:53 +03:00
next . ServeHTTP ( ctx . Resp , ctx . Req )
2021-08-04 20:26:30 +03:00
// Handle adding signedUserName to the context for the AccessLogger
usernameInterface := ctx . Data [ "SignedUserName" ]
identityPtrInterface := ctx . Req . Context ( ) . Value ( signedUserNameStringPointerKey )
if usernameInterface != nil && identityPtrInterface != nil {
username := usernameInterface . ( string )
identityPtr := identityPtrInterface . ( * string )
if identityPtr != nil && username != "" {
* identityPtr = username
}
}
2021-01-26 18:36:53 +03:00
} )
2016-03-14 00:37:44 +03:00
}
}
2016-11-15 01:33:58 +03:00
2019-03-19 05:29:43 +03:00
// NotFound handles 404s for APIContext
// String will replace message, errors will be added to a slice
func ( ctx * APIContext ) NotFound ( objs ... interface { } ) {
2022-01-20 20:46:10 +03:00
message := ctx . Tr ( "error.not_found" )
2019-03-19 05:29:43 +03:00
var errors [ ] string
for _ , obj := range objs {
2020-05-05 21:52:13 +03:00
// Ignore nil
if obj == nil {
continue
}
2019-03-19 05:29:43 +03:00
if err , ok := obj . ( error ) ; ok {
errors = append ( errors , err . Error ( ) )
} else {
message = obj . ( string )
}
}
2021-04-05 18:30:52 +03:00
ctx . JSON ( http . StatusNotFound , map [ string ] interface { } {
2021-12-28 16:28:27 +03:00
"message" : message ,
"url" : setting . API . SwaggerURL ,
"errors" : errors ,
2019-03-19 05:29:43 +03:00
} )
}
2020-11-14 19:13:55 +03:00
2022-04-21 18:17:57 +03:00
// ReferencesGitRepo injects the GitRepo into the Context
// you can optional skip the IsEmpty check
func ReferencesGitRepo ( allowEmpty ... bool ) func ( ctx * APIContext ) ( cancel context . CancelFunc ) {
return func ( ctx * APIContext ) ( cancel context . CancelFunc ) {
2020-11-14 19:13:55 +03:00
// Empty repository does not have reference information.
2022-04-21 18:17:57 +03:00
if ctx . Repo . Repository . IsEmpty && ! ( len ( allowEmpty ) != 0 && allowEmpty [ 0 ] ) {
2020-11-14 19:13:55 +03:00
return
}
2022-04-21 18:17:57 +03:00
// For API calls.
2020-11-14 19:13:55 +03:00
if ctx . Repo . GitRepo == nil {
2021-12-10 04:27:50 +03:00
repoPath := repo_model . RepoPath ( ctx . Repo . Owner . Name , ctx . Repo . Repository . Name )
2022-04-21 18:17:57 +03:00
gitRepo , err := git . OpenRepository ( ctx , repoPath )
2020-11-14 19:13:55 +03:00
if err != nil {
2022-04-21 18:17:57 +03:00
ctx . Error ( http . StatusInternalServerError , "RepoRef Invalid repo " + repoPath , err )
2020-11-14 19:13:55 +03:00
return
}
2022-04-21 18:17:57 +03:00
ctx . Repo . GitRepo = gitRepo
2020-11-14 19:13:55 +03:00
// We opened it, we should close it
2022-04-21 18:17:57 +03:00
return func ( ) {
2020-11-14 19:13:55 +03:00
// If it's been set to nil then assume someone else has closed it.
if ctx . Repo . GitRepo != nil {
ctx . Repo . GitRepo . Close ( )
}
2022-04-21 18:17:57 +03:00
}
}
2022-06-20 13:02:49 +03:00
return cancel
2022-04-21 18:17:57 +03:00
}
}
// RepoRefForAPI handles repository reference names when the ref name is not explicitly given
func RepoRefForAPI ( next http . Handler ) http . Handler {
return http . HandlerFunc ( func ( w http . ResponseWriter , req * http . Request ) {
ctx := GetAPIContext ( req )
if ctx . Repo . GitRepo == nil {
ctx . InternalServerError ( fmt . Errorf ( "no open git repo" ) )
return
}
if ref := ctx . FormTrim ( "ref" ) ; len ( ref ) > 0 {
commit , err := ctx . Repo . GitRepo . GetCommit ( ref )
if err != nil {
if git . IsErrNotExist ( err ) {
ctx . NotFound ( )
} else {
ctx . Error ( http . StatusInternalServerError , "GetBlobByPath" , err )
}
return
}
ctx . Repo . Commit = commit
2022-04-26 20:15:45 +03:00
ctx . Repo . TreePath = ctx . Params ( "*" )
2022-04-21 18:17:57 +03:00
return
2020-11-14 19:13:55 +03:00
}
2022-04-21 18:17:57 +03:00
var err error
2020-11-14 19:13:55 +03:00
refName := getRefName ( ctx . Context , RepoRefAny )
if ctx . Repo . GitRepo . IsBranchExist ( refName ) {
ctx . Repo . Commit , err = ctx . Repo . GitRepo . GetBranchCommit ( refName )
if err != nil {
ctx . InternalServerError ( err )
return
}
ctx . Repo . CommitID = ctx . Repo . Commit . ID . String ( )
} else if ctx . Repo . GitRepo . IsTagExist ( refName ) {
ctx . Repo . Commit , err = ctx . Repo . GitRepo . GetTagCommit ( refName )
if err != nil {
ctx . InternalServerError ( err )
return
}
ctx . Repo . CommitID = ctx . Repo . Commit . ID . String ( )
2022-12-27 16:12:49 +03:00
} else if len ( refName ) == git . SHAFullLength {
2020-11-14 19:13:55 +03:00
ctx . Repo . CommitID = refName
ctx . Repo . Commit , err = ctx . Repo . GitRepo . GetCommit ( refName )
if err != nil {
ctx . NotFound ( "GetCommit" , err )
return
}
} else {
ctx . NotFound ( fmt . Errorf ( "not exist: '%s'" , ctx . Params ( "*" ) ) )
return
}
2021-01-26 18:36:53 +03:00
next . ServeHTTP ( w , req )
} )
2020-11-14 19:13:55 +03:00
}