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 (
2016-03-14 06:20:22 +03:00
"fmt"
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"
2019-03-27 12:33:00 +03:00
2019-08-23 19:40:30 +03:00
"gitea.com/macaron/csrf"
"gitea.com/macaron/macaron"
2016-03-14 00:37:44 +03:00
)
2016-11-25 09:51:01 +03:00
// APIContext is a specific macaron 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 { }
2017-08-21 14:13:47 +03:00
//APIRedirect is a redirect response
// swagger:response redirect
type APIRedirect struct { }
2016-03-14 01:49:16 +03:00
// Error responses error message to client with given message.
// 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 {
message = obj . ( string )
}
if status == 500 {
2019-04-02 10:48:31 +03:00
log . Error ( "%s: %s" , title , 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
} )
}
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 ) {
links := genAPILinks ( ctx . Req . URL , total , pageSize , ctx . QueryInt ( "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 {
csrf . Validate ( ctx . Context . Context , ctx . csrf )
} else {
ctx . Context . Error ( 401 )
}
}
2019-04-19 11:59:26 +03:00
// CheckForOTP validateds OTP
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
}
ctx . Context . Error ( 500 )
return
}
ok , err := twofa . ValidateTOTP ( otpHeader )
if err != nil {
ctx . Context . Error ( 500 )
return
}
if ! ok {
ctx . Context . Error ( 401 )
return
}
}
2016-11-25 09:51:01 +03:00
// APIContexter returns apicontext as macaron middleware
2016-03-14 00:37:44 +03:00
func APIContexter ( ) macaron . Handler {
return func ( c * Context ) {
ctx := & APIContext {
Context : c ,
}
c . Map ( ctx )
}
}
2016-11-15 01:33:58 +03:00
2016-12-02 14:10:39 +03:00
// ReferencesGitRepo injects the GitRepo into the Context
2019-04-17 08:31:08 +03:00
func ReferencesGitRepo ( allowEmpty bool ) macaron . Handler {
2016-12-02 14:10:39 +03:00
return func ( ctx * APIContext ) {
// Empty repository does not have reference information.
2019-04-17 08:31:08 +03:00
if ! allowEmpty && ctx . Repo . Repository . IsEmpty {
2016-12-02 14:10:39 +03:00
return
}
// 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 {
ctx . Error ( 500 , "RepoRef Invalid repo " + repoPath , err )
return
}
ctx . Repo . GitRepo = gitRepo
2019-11-13 10:01:19 +03:00
// 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 ( )
}
} ( )
2016-12-02 14:10:39 +03:00
}
2019-11-13 10:01:19 +03:00
ctx . Next ( )
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 )
}
}
ctx . JSON ( 404 , map [ string ] interface { } {
"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 ,
} )
}