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"
2023-05-21 04:50:53 +03:00
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
mc "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"
2023-05-21 04:50:53 +03:00
"gitea.com/go-chi/cache"
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 {
2023-05-21 04:50:53 +03:00
* Base
Cache cache . Cache
Doer * user_model . User // current signed-in user
IsSigned bool
IsBasicAuth bool
ContextUser * user_model . User // the user which is being visited, in most cases it differs from Doer
Repo * Repository
Org * APIOrganization
Package * Package
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
// 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" )
2023-05-21 04:50:53 +03:00
twofa , err := auth . GetTwoFactorByUID ( ctx . 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
}
2023-05-21 04:50:53 +03:00
ctx . Error ( http . StatusInternalServerError , "GetTwoFactorByUID" , err )
2019-04-19 11:59:26 +03:00
return
}
ok , err := twofa . ValidateTOTP ( otpHeader )
if err != nil {
2023-05-21 04:50:53 +03:00
ctx . Error ( http . StatusInternalServerError , "ValidateTOTP" , err )
2019-04-19 11:59:26 +03:00
return
}
if ! ok {
2023-05-21 04:50:53 +03:00
ctx . Error ( http . StatusUnauthorized , "" , nil )
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 ) {
2023-05-21 04:50:53 +03:00
base , baseCleanUp := NewBaseContext ( w , req )
ctx := & APIContext {
Base : base ,
Cache : mc . GetCache ( ) ,
Repo : & Repository { PullRequest : & PullRequest { } } ,
Org : & APIOrganization { } ,
2021-01-26 18:36:53 +03:00
}
2023-05-21 04:50:53 +03:00
defer baseCleanUp ( )
2021-01-26 18:36:53 +03:00
2023-05-21 04:50:53 +03:00
ctx . Base . AppendContextValue ( apiContextKey , ctx )
ctx . Base . AppendContextValueFunc ( git . RepositoryContextKey , func ( ) any { return ctx . Repo . GitRepo } )
2021-01-26 18:36:53 +03:00
// 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
next . ServeHTTP ( ctx . Resp , ctx . Req )
} )
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 {
2023-05-21 04:50:53 +03:00
_ = ctx . Repo . GitRepo . Close ( )
2020-11-14 19:13:55 +03:00
}
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 {
2023-04-26 11:14:35 +03:00
ctx . Error ( http . StatusInternalServerError , "GetCommit" , err )
2022-04-21 18:17:57 +03:00
}
return
}
ctx . Repo . Commit = commit
2022-04-26 20:15:45 +03:00
ctx . Repo . TreePath = ctx . Params ( "*" )
2023-04-27 09:06:45 +03:00
next . ServeHTTP ( w , req )
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
2023-05-21 04:50:53 +03:00
refName := getRefName ( ctx . Base , ctx . Repo , RepoRefAny )
2020-11-14 19:13:55 +03:00
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
}
2023-05-21 04:50:53 +03:00
// HasAPIError returns true if error occurs in form validation.
func ( ctx * APIContext ) HasAPIError ( ) bool {
hasErr , ok := ctx . Data [ "HasError" ]
if ! ok {
return false
}
return hasErr . ( bool )
}
// GetErrMsg returns error message in form validation.
func ( ctx * APIContext ) GetErrMsg ( ) string {
msg , _ := ctx . Data [ "ErrorMsg" ] . ( string )
if msg == "" {
msg = "invalid form data"
}
return msg
}
// NotFoundOrServerError use error check function to determine if the error
// is about not found. It responds with 404 status code for not found error,
// or error context description for logging purpose of 500 server error.
func ( ctx * APIContext ) NotFoundOrServerError ( logMsg string , errCheck func ( error ) bool , logErr error ) {
if errCheck ( logErr ) {
ctx . JSON ( http . StatusNotFound , nil )
return
}
ctx . Error ( http . StatusInternalServerError , "NotFoundOrServerError" , logMsg )
}
// IsUserSiteAdmin returns true if current user is a site admin
func ( ctx * APIContext ) IsUserSiteAdmin ( ) bool {
return ctx . IsSigned && ctx . Doer . IsAdmin
}
// IsUserRepoAdmin returns true if current user is admin in current repo
func ( ctx * APIContext ) IsUserRepoAdmin ( ) bool {
return ctx . Repo . IsAdmin ( )
}
// IsUserRepoWriter returns true if current user has write privilege in current repo
func ( ctx * APIContext ) IsUserRepoWriter ( unitTypes [ ] unit . Type ) bool {
for _ , unitType := range unitTypes {
if ctx . Repo . CanWrite ( unitType ) {
return true
}
}
return false
}