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.
2016-03-14 00:37:44 +03:00
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package context
import (
2021-01-26 18:36:53 +03:00
"context"
2016-03-14 06:20:22 +03:00
"fmt"
2021-01-26 18:36:53 +03:00
"html"
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"
2016-11-15 01:33:58 +03:00
"code.gitea.io/gitea/models"
2019-03-27 12:33:00 +03:00
"code.gitea.io/gitea/modules/git"
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"
2021-06-09 20:53:16 +03:00
"code.gitea.io/gitea/services/auth"
2019-03-27 12:33:00 +03:00
2021-01-26 18:36:53 +03:00
"gitea.com/go-chi/session"
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
}
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 {
Topics [ ] string ` json:"invalidTopics" `
Message string ` json:"message" `
}
2017-05-18 17:39:42 +03:00
//APIEmpty is an empty response
2017-05-02 16:35:59 +03:00
// swagger:response empty
type APIEmpty struct { }
//APIForbiddenError is a forbidden error response
// swagger:response forbidden
type APIForbiddenError struct {
APIError
}
//APINotFound is a not found empty response
// swagger:response notFound
type APINotFound struct { }
2020-10-31 04:56:34 +03:00
//APIConflict is a conflict empty response
// swagger:response conflict
type APIConflict struct { }
2017-08-21 14:13:47 +03:00
//APIRedirect is a redirect response
// swagger:response redirect
type APIRedirect struct { }
2020-06-05 14:03:12 +03:00
//APIString is a string response
// 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
2021-01-26 18:36:53 +03:00
if setting . IsProd ( ) && ! ( ctx . User != nil && ctx . User . 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
2021-01-26 18:36:53 +03:00
if ! setting . IsProd ( ) || ( ctx . User != nil && ctx . User . IsAdmin ) {
2020-05-28 19:58:11 +03:00
message = err . Error ( )
}
ctx . JSON ( http . StatusInternalServerError , APIError {
Message : message ,
URL : setting . API . SwaggerURL ,
} )
}
2021-01-26 18:36:53 +03:00
var (
apiContextKey interface { } = "default_api_context"
)
// 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 {
ctx . Header ( ) . Set ( "Link" , strings . Join ( links , "," ) )
}
}
2018-11-04 04:15:55 +03:00
// RequireCSRF requires a validated a CSRF token
func ( ctx * APIContext ) RequireCSRF ( ) {
headerToken := ctx . Req . Header . Get ( ctx . csrf . GetHeaderName ( ) )
formValueToken := ctx . Req . FormValue ( ctx . csrf . GetFormName ( ) )
if len ( headerToken ) > 0 || len ( formValueToken ) > 0 {
2021-01-26 18:36:53 +03:00
Validate ( ctx . Context , ctx . csrf )
2018-11-04 04:15:55 +03:00
} else {
2020-11-23 10:56:04 +03:00
ctx . Context . Error ( 401 , "Missing CSRF token." )
2018-11-04 04:15:55 +03:00
}
}
2020-06-22 18:21:11 +03:00
// CheckForOTP validates OTP
2019-04-19 11:59:26 +03:00
func ( ctx * APIContext ) CheckForOTP ( ) {
otpHeader := ctx . Req . Header . Get ( "X-Gitea-OTP" )
twofa , err := models . GetTwoFactorByUID ( ctx . Context . User . ID )
if err != nil {
if models . IsErrTwoFactorNotEnrolled ( err ) {
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 {
ctx . Context . Error ( 401 )
return
}
}
2021-06-09 20:53:16 +03:00
// APIAuth converts auth.Auth as a middleware
2021-07-24 13:16:34 +03:00
func APIAuth ( authMethod auth . Method ) func ( * APIContext ) {
2021-06-09 20:53:16 +03:00
return func ( ctx * APIContext ) {
// Get user from session if logged in.
ctx . User = authMethod . Verify ( ctx . Req , ctx . Resp , ctx , ctx . Session )
if ctx . User != nil {
ctx . IsBasicAuth = ctx . Data [ "AuthedMethod" ] . ( string ) == new ( auth . Basic ) . Name ( )
ctx . IsSigned = true
ctx . Data [ "IsSigned" ] = ctx . IsSigned
ctx . Data [ "SignedUser" ] = ctx . User
ctx . Data [ "SignedUserID" ] = ctx . User . ID
ctx . Data [ "SignedUserName" ] = ctx . User . Name
ctx . Data [ "IsAdmin" ] = ctx . User . IsAdmin
} else {
ctx . Data [ "SignedUserID" ] = int64 ( 0 )
ctx . Data [ "SignedUserName" ] = ""
}
}
}
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 {
var csrfOpts = getCsrfOpts ( )
return func ( next http . Handler ) http . Handler {
return http . HandlerFunc ( func ( w http . ResponseWriter , req * http . Request ) {
2021-01-30 11:55:53 +03:00
var locale = middleware . Locale ( w , req )
2021-01-26 18:36:53 +03:00
var ctx = APIContext {
Context : & Context {
Resp : NewResponse ( w ) ,
Data : map [ string ] interface { } { } ,
Locale : locale ,
Session : session . GetSession ( req ) ,
Repo : & Repository {
PullRequest : & PullRequest { } ,
} ,
Org : & Organization { } ,
} ,
Org : & APIOrganization { } ,
}
ctx . Req = WithAPIContext ( WithContext ( req , ctx . Context ) , & ctx )
ctx . csrf = Csrfer ( csrfOpts , ctx . Context )
// 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
}
}
ctx . Resp . Header ( ) . Set ( ` X-Frame-Options ` , ` SAMEORIGIN ` )
ctx . Data [ "CsrfToken" ] = html . EscapeString ( ctx . csrf . GetToken ( ) )
next . ServeHTTP ( ctx . Resp , ctx . Req )
} )
2016-03-14 00:37:44 +03:00
}
}
2016-11-15 01:33:58 +03:00
2016-12-02 14:10:39 +03:00
// ReferencesGitRepo injects the GitRepo into the Context
2021-01-26 18:36:53 +03:00
func ReferencesGitRepo ( allowEmpty bool ) func ( http . Handler ) http . Handler {
return func ( next http . Handler ) http . Handler {
return http . HandlerFunc ( func ( w http . ResponseWriter , req * http . Request ) {
ctx := GetAPIContext ( req )
// Empty repository does not have reference information.
if ! allowEmpty && ctx . Repo . Repository . IsEmpty {
2016-12-02 14:10:39 +03:00
return
}
2021-01-26 18:36:53 +03:00
// For API calls.
if ctx . Repo . GitRepo == nil {
repoPath := models . RepoPath ( ctx . Repo . Owner . Name , ctx . Repo . Repository . Name )
gitRepo , err := git . OpenRepository ( repoPath )
if err != nil {
2021-04-05 18:30:52 +03:00
ctx . Error ( http . StatusInternalServerError , "RepoRef Invalid repo " + repoPath , err )
2021-01-26 18:36:53 +03:00
return
2019-11-13 10:01:19 +03:00
}
2021-01-26 18:36:53 +03:00
ctx . Repo . GitRepo = gitRepo
// We opened it, we should close it
defer func ( ) {
// If it's been set to nil then assume someone else has closed it.
if ctx . Repo . GitRepo != nil {
ctx . Repo . GitRepo . Close ( )
}
} ( )
}
2019-11-13 10:01:19 +03:00
2021-01-26 18:36:53 +03:00
next . ServeHTTP ( w , req )
} )
2016-12-02 14:10:39 +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 { } ) {
var message = "Not Found"
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 { } {
2019-03-19 05:29:43 +03:00
"message" : message ,
2019-06-13 00:07:24 +03:00
"documentation_url" : setting . API . SwaggerURL ,
2019-03-19 05:29:43 +03:00
"errors" : errors ,
} )
}
2020-11-14 19:13:55 +03:00
// RepoRefForAPI handles repository reference names when the ref name is not explicitly given
2021-01-26 18:36:53 +03:00
func RepoRefForAPI ( next http . Handler ) http . Handler {
return http . HandlerFunc ( func ( w http . ResponseWriter , req * http . Request ) {
ctx := GetAPIContext ( req )
2020-11-14 19:13:55 +03:00
// Empty repository does not have reference information.
if ctx . Repo . Repository . IsEmpty {
return
}
var err error
if ctx . Repo . GitRepo == nil {
repoPath := models . RepoPath ( ctx . Repo . Owner . Name , ctx . Repo . Repository . Name )
ctx . Repo . GitRepo , err = git . OpenRepository ( repoPath )
if err != nil {
ctx . InternalServerError ( err )
return
}
// We opened it, we should close it
defer func ( ) {
// If it's been set to nil then assume someone else has closed it.
if ctx . Repo . GitRepo != nil {
ctx . Repo . GitRepo . Close ( )
}
} ( )
}
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 ( )
} else if len ( refName ) == 40 {
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
}